dmenu-4.6004075500017500001750000000000001261774771500120455ustar00hiltjohiltjodmenu-4.6/LICENSE010064400017500001750000000026421261774771500131320ustar00hiltjohiltjoMIT/X Consortium License © 2006-2014 Anselm R Garbe © 2010-2012 Connor Lane Smith © 2009 Gottox © 2009 Markus Schnalke © 2009 Evan Gates © 2006-2008 Sander van Dijk © 2006-2007 Michał Janeczek © 2014-2015 Hiltjo Posthuma Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dmenu-4.6/Makefile010064400017500001750000000041221261774771500135600ustar00hiltjohiltjo# dmenu - dynamic menu # See LICENSE file for copyright and license details. include config.mk SRC = drw.c dmenu.c stest.c util.c OBJ = ${SRC:.c=.o} all: options dmenu stest options: @echo dmenu build options: @echo "CFLAGS = ${CFLAGS}" @echo "LDFLAGS = ${LDFLAGS}" @echo "CC = ${CC}" .c.o: @echo CC $< @${CC} -c ${CFLAGS} $< config.h: @echo creating $@ from config.def.h @cp config.def.h $@ ${OBJ}: arg.h config.h config.mk drw.h dmenu: dmenu.o drw.o util.o @echo CC -o $@ @${CC} -o $@ dmenu.o drw.o util.o ${LDFLAGS} stest: stest.o @echo CC -o $@ @${CC} -o $@ stest.o ${LDFLAGS} clean: @echo cleaning @rm -f dmenu stest ${OBJ} dmenu-${VERSION}.tar.gz dist: clean @echo creating dist tarball @mkdir -p dmenu-${VERSION} @cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1 \ drw.h util.h dmenu_path dmenu_run stest.1 ${SRC} \ dmenu-${VERSION} @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION} @gzip dmenu-${VERSION}.tar @rm -rf dmenu-${VERSION} install: all @echo installing executables to ${DESTDIR}${PREFIX}/bin @mkdir -p ${DESTDIR}${PREFIX}/bin @cp -f dmenu dmenu_path dmenu_run stest ${DESTDIR}${PREFIX}/bin @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_path @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run @chmod 755 ${DESTDIR}${PREFIX}/bin/stest @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1 @sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.1 uninstall: @echo removing executables from ${DESTDIR}${PREFIX}/bin @rm -f ${DESTDIR}${PREFIX}/bin/dmenu @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_path @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run @rm -f ${DESTDIR}${PREFIX}/bin/stest @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 @rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1 @rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1 .PHONY: all options clean dist install uninstall dmenu-4.6/README010064400017500001750000000007331261774771500130040ustar00hiltjohiltjodmenu - dynamic menu ==================== dmenu is an efficient dynamic menu for X. Requirements ------------ In order to build dmenu you need the Xlib header files. Installation ------------ Edit config.mk to match your local setup (dmenu is installed into the /usr/local namespace by default). Afterwards enter the following command to build and install dmenu (if necessary as root): make clean install Running dmenu ------------- See the man page for details. dmenu-4.6/arg.h010064400017500001750000000017631261774771500130520ustar00hiltjohiltjo/* * Copy me if you can. * by 20h */ #ifndef ARG_H__ #define ARG_H__ extern char *argv0; /* use main(int argc, char *argv[]) */ #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ argv[0] && argv[0][1]\ && argv[0][0] == '-';\ argc--, argv++) {\ char argc_;\ char **argv_;\ int brk_;\ if (argv[0][1] == '-' && argv[0][2] == '\0') {\ argv++;\ argc--;\ break;\ }\ for (brk_ = 0, argv[0]++, argv_ = argv;\ argv[0][0] && !brk_;\ argv[0]++) {\ if (argv_ != argv)\ break;\ argc_ = argv[0][0];\ switch (argc_) #define ARGEND }\ } #define ARGC() argc_ #define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ ((x), abort(), (char *)0) :\ (brk_ = 1, (argv[0][1] != '\0')?\ (&argv[0][1]) :\ (argc--, argv++, argv[0]))) #define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ (char *)0 :\ (brk_ = 1, (argv[0][1] != '\0')?\ (&argv[0][1]) :\ (argc--, argv++, argv[0]))) #endif dmenu-4.6/config.def.h010064400017500001750000000017731261774771500143040ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ /* Default settings; can be overriden by command line. */ static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ /* -fn option overrides fonts[0]; default X11 font or font set */ static const char *fonts[] = { "monospace:size=10" }; static const char *prompt = NULL; /* -p option; prompt to the elft of input field */ static const char *normbgcolor = "#222222"; /* -nb option; normal background */ static const char *normfgcolor = "#bbbbbb"; /* -nf option; normal foreground */ static const char *selbgcolor = "#005577"; /* -sb option; selected background */ static const char *selfgcolor = "#eeeeee"; /* -sf option; selected foreground */ static const char *outbgcolor = "#00ffff"; static const char *outfgcolor = "#000000"; /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ static unsigned int lines = 0; dmenu-4.6/config.mk010064400017500001750000000013301261774771500137140ustar00hiltjohiltjo# dmenu version VERSION = 4.6 # paths PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib # Xinerama, comment if you don't want it XINERAMALIBS = -lXinerama XINERAMAFLAGS = -DXINERAMA # freetype FREETYPELIBS = -lfontconfig -lXft FREETYPEINC = /usr/include/freetype2 # OpenBSD (uncomment) FREETYPEINC = ${X11INC}/freetype2 # includes and libs INCS = -I${X11INC} -I${FREETYPEINC} LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} # flags CPPFLAGS = -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} LDFLAGS = -s ${LIBS} # compiler and linker CC = cc dmenu-4.6/dmenu.1010064400017500001750000000053151261774771500133170ustar00hiltjohiltjo.TH DMENU 1 dmenu\-VERSION .SH NAME dmenu \- dynamic menu .SH SYNOPSIS .B dmenu .RB [ \-b ] .RB [ \-f ] .RB [ \-i ] .RB [ \-l .RB [ \-m .IR monitor ] .IR lines ] .RB [ \-p .IR prompt ] .RB [ \-fn .IR font ] .RB [ \-nb .IR color ] .RB [ \-nf .IR color ] .RB [ \-sb .IR color ] .RB [ \-sf .IR color ] .RB [ \-v ] .P .BR dmenu_run " ..." .SH DESCRIPTION .B dmenu is a dynamic menu for X, which reads a list of newline\-separated items from stdin. When the user selects an item and presses Return, their choice is printed to stdout and dmenu terminates. Entering text will narrow the items to those matching the tokens in the input. .P .B dmenu_run is a script used by .IR dwm (1) which lists programs in the user's $PATH and runs the result in their $SHELL. .SH OPTIONS .TP .B \-b dmenu appears at the bottom of the screen. .TP .B \-f dmenu grabs the keyboard before reading stdin. This is faster, but will lock up X until stdin reaches end\-of\-file. .TP .B \-i dmenu matches menu items case insensitively. .TP .BI \-l " lines" dmenu lists items vertically, with the given number of lines. .TP .BI \-m " monitor" dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0. .TP .BI \-p " prompt" defines the prompt to be displayed to the left of the input field. .TP .BI \-fn " font" defines the font or font set used. .TP .BI \-nb " color" defines the normal background color. .IR #RGB , .IR #RRGGBB , and X color names are supported. .TP .BI \-nf " color" defines the normal foreground color. .TP .BI \-sb " color" defines the selected background color. .TP .BI \-sf " color" defines the selected foreground color. .TP .B \-v prints version information to stdout, then exits. .SH USAGE dmenu is completely controlled by the keyboard. Items are selected using the arrow keys, page up, page down, home, and end. .TP .B Tab Copy the selected item to the input field. .TP .B Return Confirm selection. Prints the selected item to stdout and exits, returning success. .TP .B Ctrl-Return Confirm selection. Prints the selected item to stdout and continues. .TP .B Shift\-Return Confirm input. Prints the input text to stdout and exits, returning success. .TP .B Escape Exit without selecting an item, returning failure. .TP C\-a Home .TP C\-b Left .TP C\-c Escape .TP C\-d Delete .TP C\-e End .TP C\-f Right .TP C\-g Escape .TP C\-h Backspace .TP C\-i Tab .TP C\-j Return .TP C\-J Shift-Return .TP C\-k Delete line right .TP C\-m Return .TP C\-M Shift-Return .TP C\-n Down .TP C\-p Up .TP C\-u Delete line left .TP C\-w Delete word left .TP C\-y Paste from primary X selection .TP C\-Y Paste from X clipboard .TP M\-g Home .TP M\-G End .TP M\-h Up .TP M\-j Page down .TP M\-k Page up .TP M\-l Down .SH SEE ALSO .IR dwm (1), .IR stest (1) dmenu-4.6/drw.h010064400017500001750000000031531261774771500130700ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ #define DRW_FONT_CACHE_SIZE 32 typedef struct { unsigned long pix; XftColor rgb; } Clr; typedef struct { Cursor cursor; } Cur; typedef struct { Display *dpy; int ascent; int descent; unsigned int h; XftFont *xfont; FcPattern *pattern; } Fnt; typedef struct { Clr *fg; Clr *bg; Clr *border; } ClrScheme; typedef struct { unsigned int w, h; Display *dpy; int screen; Window root; Drawable drawable; GC gc; ClrScheme *scheme; size_t fontcount; Fnt *fonts[DRW_FONT_CACHE_SIZE]; } Drw; typedef struct { unsigned int w; unsigned int h; } Extnts; /* Drawable abstraction */ Drw *drw_create(Display *, int, Window, unsigned int, unsigned int); void drw_resize(Drw *, unsigned int, unsigned int); void drw_free(Drw *); /* Fnt abstraction */ Fnt *drw_font_create(Drw *, const char *); void drw_load_fonts(Drw *, const char *[], size_t); void drw_font_free(Fnt *); void drw_font_getexts(Fnt *, const char *, unsigned int, Extnts *); unsigned int drw_font_getexts_width(Fnt *, const char *, unsigned int); /* Colour abstraction */ Clr *drw_clr_create(Drw *, const char *); void drw_clr_free(Clr *); /* Cursor abstraction */ Cur *drw_cur_create(Drw *, int); void drw_cur_free(Drw *, Cur *); /* Drawing context manipulation */ void drw_setfont(Drw *, Fnt *); void drw_setscheme(Drw *, ClrScheme *); /* Drawing functions */ void drw_rect(Drw *, int, int, unsigned int, unsigned int, int, int, int); int drw_text(Drw *, int, int, unsigned int, unsigned int, const char *, int); /* Map functions */ void drw_map(Drw *, Window, int, int, unsigned int, unsigned int); dmenu-4.6/util.h010064400017500001750000000004511261774771500132470ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) void die(const char *errstr, ...); void *ecalloc(size_t, size_t); dmenu-4.6/dmenu_path010064400017500001750000000004461261774771500141740ustar00hiltjohiltjo#!/bin/sh cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"} if [ -d "$cachedir" ]; then cache=$cachedir/dmenu_run else cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~ fi IFS=: if stest -dqr -n "$cache" $PATH; then stest -flx $PATH | sort -u | tee "$cache" else cat "$cache" fi dmenu-4.6/dmenu_run010075500017500001750000000000721261774771500140420ustar00hiltjohiltjo#!/bin/sh dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & dmenu-4.6/stest.1010064400017500001750000000027721261774771500133550ustar00hiltjohiltjo.TH STEST 1 dmenu\-VERSION .SH NAME stest \- filter a list of files by properties .SH SYNOPSIS .B stest .RB [ -abcdefghlpqrsuwx ] .RB [ -n .IR file ] .RB [ -o .IR file ] .RI [ file ...] .SH DESCRIPTION .B stest takes a list of files and filters by the files' properties, analogous to .IR test (1). Files which pass all tests are printed to stdout. If no files are given, stest reads files from stdin. .SH OPTIONS .TP .B \-a Test hidden files. .TP .B \-b Test that files are block specials. .TP .B \-c Test that files are character specials. .TP .B \-d Test that files are directories. .TP .B \-e Test that files exist. .TP .B \-f Test that files are regular files. .TP .B \-g Test that files have their set-group-ID flag set. .TP .B \-h Test that files are symbolic links. .TP .B \-l Test the contents of a directory given as an argument. .TP .BI \-n " file" Test that files are newer than .IR file . .TP .BI \-o " file" Test that files are older than .IR file . .TP .B \-p Test that files are named pipes. .TP .B \-q No files are printed, only the exit status is returned. .TP .B \-r Test that files are readable. .TP .B \-s Test that files are not empty. .TP .B \-u Test that files have their set-user-ID flag set. .TP .B \-v Invert the sense of tests, only failing files pass. .TP .B \-w Test that files are writable. .TP .B \-x Test that files are executable. .SH EXIT STATUS .TP .B 0 At least one file passed all tests. .TP .B 1 No files passed all tests. .TP .B 2 An error occurred. .SH SEE ALSO .IR dmenu (1), .IR test (1) dmenu-4.6/drw.c010064400017500001750000000224431261774771500130660ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include "drw.h" #include "util.h" #define UTF_INVALID 0xFFFD #define UTF_SIZ 4 static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 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) { for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) return (unsigned char)c & ~utfmask[*i]; return 0; } static size_t utf8validate(long *u, size_t i) { 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; *u = UTF_INVALID; if (!clen) return 0; udecoded = utf8decodebyte(c[0], &len); if (!BETWEEN(len, 1, UTF_SIZ)) return 1; for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); if (type) return j; } if (j < len) return 0; *u = udecoded; utf8validate(u, len); return len; } Drw * drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) { Drw *drw; drw = ecalloc(1, sizeof(Drw)); drw->dpy = dpy; drw->screen = screen; drw->root = root; drw->w = w; drw->h = h; drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); drw->gc = XCreateGC(dpy, root, 0, NULL); drw->fontcount = 0; XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); return drw; } void drw_resize(Drw *drw, unsigned int w, unsigned int h) { drw->w = w; drw->h = h; if (drw->drawable) XFreePixmap(drw->dpy, drw->drawable); drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); } void 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); XFreeGC(drw->dpy, drw->gc); free(drw); } /* This function is an implementation detail. Library users should use * drw_font_create instead. */ static Fnt * drw_font_xcreate(Drw *drw, const char *fontname, FcPattern *fontpattern) { Fnt *font; XftFont *xfont = NULL; FcPattern *pattern = NULL; if (fontname) { /* Using the pattern found at font->xfont->pattern does not yield same * the same substitution results as using the pattern returned by * FcNameParse; using the latter results in the desired fallback * behaviour whereas the former just results in * missing-character-rectangles being drawn, at least with some fonts. */ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { fprintf(stderr, "error, cannot load font: '%s'\n", fontname); return NULL; } if (!(pattern = FcNameParse((FcChar8 *) fontname))) { fprintf(stderr, "error, cannot load font: '%s'\n", fontname); XftFontClose(drw->dpy, xfont); return NULL; } } else if (fontpattern) { if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { fprintf(stderr, "error, cannot load font pattern.\n"); return NULL; } } else { die("no font specified.\n"); } font = ecalloc(1, sizeof(Fnt)); font->xfont = xfont; font->pattern = pattern; font->ascent = xfont->ascent; font->descent = xfont->descent; font->h = font->ascent + font->descent; font->dpy = drw->dpy; return font; } Fnt* 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) return; if (font->pattern) FcPatternDestroy(font->pattern); XftFontClose(font->dpy, font->xfont); free(font); } Clr * drw_clr_create(Drw *drw, const char *clrname) { Clr *clr; clr = ecalloc(1, sizeof(Clr)); if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen), clrname, &clr->rgb)) die("error, cannot allocate color '%s'\n", clrname); clr->pix = clr->rgb.pixel; return clr; } void drw_clr_free(Clr *clr) { free(clr); } void drw_setscheme(Drw *drw, ClrScheme *scheme) { drw->scheme = scheme; } void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int empty, int invert) { if (!drw->scheme) return; XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg->pix : drw->scheme->fg->pix); if (filled) XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w + 1, h + 1); else if (empty) XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); } int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int invert) { char buf[1024]; int tx, ty, th; Extnts tex; XftDraw *d = NULL; Fnt *curfont, *nextfont; size_t i, len; int utf8strlen, utf8charlen, render; long utf8codepoint = 0; const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; int charexists = 0; if (!drw->scheme || !drw->fontcount) return 0; if (!(render = x || y || w || h)) { w = ~w; } else { 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); d = XftDrawCreate(drw->dpy, drw->drawable, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen)); } curfont = drw->fonts[0]; while (1) { utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); for (i = 0; i < drw->fontcount; i++) { charexists = charexists || XftCharExists(drw->dpy, drw->fonts[i]->xfont, utf8codepoint); if (charexists) { if (drw->fonts[i] == curfont) { utf8strlen += utf8charlen; text += utf8charlen; } else { nextfont = drw->fonts[i]; } break; } } if (!charexists || (nextfont && nextfont != curfont)) break; else charexists = 0; } if (utf8strlen) { drw_font_getexts(curfont, utf8str, utf8strlen, &tex); /* shorten text if necessary */ for (len = MIN(utf8strlen, (sizeof buf) - 1); len && (tex.w > w - drw->fonts[0]->h || w < drw->fonts[0]->h); len--) drw_font_getexts(curfont, utf8str, len, &tex); 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; } else if (nextfont) { charexists = 0; curfont = nextfont; } else { /* Regardless of whether or not a fallback font is found, the * character must be drawn. */ charexists = 1; if (drw->fontcount >= DRW_FONT_CACHE_SIZE) continue; fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); if (!drw->fonts[0]->pattern) { /* Refer to the comment in drw_font_xcreate for more * information. */ die("the first font in the cache must be loaded from a font string.\n"); } fcpattern = FcPatternDuplicate(drw->fonts[0]->pattern); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); FcDefaultSubstitute(fcpattern); match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); FcCharSetDestroy(fccharset); FcPatternDestroy(fcpattern); if (match) { curfont = drw_font_xcreate(drw, NULL, match); if (curfont && XftCharExists(drw->dpy, curfont->xfont, utf8codepoint)) { drw->fonts[drw->fontcount++] = curfont; } else { drw_font_free(curfont); curfont = drw->fonts[0]; } } } } if (d) XftDrawDestroy(d); return x; } void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) { XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); XSync(drw->dpy, False); } void drw_font_getexts(Fnt *font, const char *text, unsigned int len, Extnts *tex) { XGlyphInfo ext; XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); tex->h = font->h; tex->w = ext.xOff; } 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 * drw_cur_create(Drw *drw, int shape) { Cur *cur; cur = ecalloc(1, sizeof(Cur)); cur->cursor = XCreateFontCursor(drw->dpy, shape); return cur; } void drw_cur_free(Drw *drw, Cur *cursor) { if (!cursor) return; XFreeCursor(drw->dpy, cursor->cursor); free(cursor); } dmenu-4.6/dmenu.c010064400017500001750000000410201261774771500133720ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #ifdef XINERAMA #include #endif #include #include "drw.h" #include "util.h" /* macros */ #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) #define 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 */ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ struct item { char *text; struct item *left, *right; int out; }; static char text[BUFSIZ] = ""; static int bh, mw, mh; static int sw, sh; /* X display screen geometry width, height */ static int inputw, promptw; static size_t cursor; static struct item *items = NULL; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel; static int mon = -1, screen; static Atom clip, utf8; static Display *dpy; static Window root, win; static XIC xic; static ClrScheme scheme[SchemeLast]; static Drw *drw; #include "config.h" static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; static void appenditem(struct item *item, struct item **list, struct item **last) { if (*last) (*last)->right = item; else *list = item; item->left = *last; item->right = NULL; *last = item; } static void calcoffsets(void) { int i, n; if (lines > 0) n = lines * bh; else n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); /* calculate which items will begin the next page and previous page */ for (i = 0, next = curr; next; next = next->right) if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) break; for (i = 0, prev = curr; prev && prev->left; prev = prev->left) if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) break; } static void cleanup(void) { size_t i; XUngrabKey(dpy, AnyKey, AnyModifier, root); for (i = 0; i < SchemeLast; i++) { drw_clr_free(scheme[i].bg); drw_clr_free(scheme[i].fg); } drw_free(drw); XSync(dpy, False); XCloseDisplay(dpy); } static char * cistrstr(const char *s, const char *sub) { size_t len; for (len = strlen(sub); *s; s++) if (!strncasecmp(s, sub, len)) return (char *)s; return NULL; } static void drawmenu(void) { int curpos; struct item *item; int x = 0, y = 0, h = bh, w; drw_setscheme(drw, &scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1, 1); if (prompt && *prompt) { drw_setscheme(drw, &scheme[SchemeSel]); drw_text(drw, x, 0, promptw, bh, prompt, 0); x += promptw; } /* draw input field */ w = (lines > 0 || !matches) ? mw - x : inputw; drw_setscheme(drw, &scheme[SchemeNorm]); drw_text(drw, x, 0, w, bh, text, 0); if ((curpos = TEXTNW(text, cursor) + bh / 2 - 2) < w) { drw_setscheme(drw, &scheme[SchemeNorm]); drw_rect(drw, x + curpos + 2, 2, 1, bh - 4, 1, 1, 0); } if (lines > 0) { /* draw vertical list */ w = 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) { /* draw horizontal list */ x += inputw; w = TEXTW("<"); if (curr->left) { drw_setscheme(drw, &scheme[SchemeNorm]); drw_text(drw, x, 0, w, bh, "<", 0); } for (item = curr; item != next; item = item->right) { x += w; 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) { drw_setscheme(drw, &scheme[SchemeNorm]); drw_text(drw, x, 0, w, bh, ">", 0); } } drw_map(drw, win, 0, 0, mw, mh); } static void grabkeyboard(void) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; int i; /* try to grab keyboard, we may have to wait for another process to ungrab */ for (i = 0; i < 1000; i++) { if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) return; nanosleep(&ts, NULL); } die("cannot grab keyboard\n"); } static void match(void) { static char **tokv = NULL; static int tokn = 0; char buf[sizeof text], *s; int i, tokc = 0; size_t len, textsize; struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; strcpy(buf, text); /* separate input text into tokens to be matched individually */ for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) die("cannot realloc %u bytes\n", tokn * sizeof *tokv); len = tokc ? strlen(tokv[0]) : 0; matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; textsize = strlen(text); for (item = items; item && item->text; item++) { for (i = 0; i < tokc; i++) if (!fstrstr(item->text, tokv[i])) break; if (i != tokc) /* not all tokens match */ continue; /* exact matches go first, then prefixes, then substrings */ if (!tokc || !fstrncmp(text, item->text, textsize)) appenditem(item, &matches, &matchend); else if (!fstrncmp(tokv[0], item->text, len)) appenditem(item, &lprefix, &prefixend); else appenditem(item, &lsubstr, &substrend); } if (lprefix) { if (matches) { matchend->right = lprefix; lprefix->left = matchend; } else matches = lprefix; matchend = prefixend; } if (lsubstr) { if (matches) { matchend->right = lsubstr; lsubstr->left = matchend; } else matches = lsubstr; matchend = substrend; } curr = sel = matches; calcoffsets(); } static void insert(const char *str, ssize_t n) { if (strlen(text) + n > sizeof text - 1) return; /* move existing text out of the way, insert new text, and update cursor */ memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); if (n > 0) memcpy(&text[cursor], str, n); cursor += n; match(); } static size_t nextrune(int inc) { ssize_t n; /* return location of next utf8 rune in the given direction (+1 or -1) */ for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) ; return n; } static void keypress(XKeyEvent *ev) { char buf[32]; int len; KeySym ksym = NoSymbol; Status status; len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); if (status == XBufferOverflow) return; if (ev->state & ControlMask) switch(ksym) { case XK_a: ksym = XK_Home; break; case XK_b: ksym = XK_Left; break; case XK_c: ksym = XK_Escape; break; case XK_d: ksym = XK_Delete; break; case XK_e: ksym = XK_End; break; case XK_f: ksym = XK_Right; break; case XK_g: ksym = XK_Escape; break; case XK_h: ksym = XK_BackSpace; break; case XK_i: ksym = XK_Tab; break; case XK_j: /* fallthrough */ case XK_J: /* fallthrough */ case XK_m: /* fallthrough */ case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; case XK_n: ksym = XK_Down; break; case XK_p: ksym = XK_Up; break; case XK_k: /* delete right */ text[cursor] = '\0'; match(); break; case XK_u: /* delete left */ insert(NULL, 0 - cursor); break; case XK_w: /* delete word */ while (cursor > 0 && text[nextrune(-1)] == ' ') insert(NULL, nextrune(-1) - cursor); while (cursor > 0 && text[nextrune(-1)] != ' ') insert(NULL, nextrune(-1) - cursor); break; case XK_y: /* paste selection */ case XK_Y: XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, utf8, utf8, win, CurrentTime); return; case XK_Return: case XK_KP_Enter: break; case XK_bracketleft: cleanup(); exit(1); default: return; } else if (ev->state & Mod1Mask) switch(ksym) { case XK_g: ksym = XK_Home; break; case XK_G: ksym = XK_End; break; case XK_h: ksym = XK_Up; break; case XK_j: ksym = XK_Next; break; case XK_k: ksym = XK_Prior; break; case XK_l: ksym = XK_Down; break; default: return; } switch(ksym) { default: if (!iscntrl(*buf)) insert(buf, len); break; case XK_Delete: if (text[cursor] == '\0') return; cursor = nextrune(+1); /* fallthrough */ case XK_BackSpace: if (cursor == 0) return; insert(NULL, nextrune(-1) - cursor); break; case XK_End: if (text[cursor] != '\0') { cursor = strlen(text); break; } if (next) { /* jump to end of list and position items in reverse */ curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while (next && (curr = curr->right)) calcoffsets(); } sel = matchend; break; case XK_Escape: cleanup(); exit(1); case XK_Home: if (sel == matches) { cursor = 0; break; } sel = curr = matches; calcoffsets(); break; case XK_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; } if (lines > 0) return; /* fallthrough */ case XK_Up: if (sel && sel->left && (sel = sel->left)->right == curr) { curr = prev; calcoffsets(); } break; case XK_Next: if (!next) return; sel = curr = next; calcoffsets(); break; case XK_Prior: if (!prev) return; sel = curr = prev; calcoffsets(); break; case XK_Return: case XK_KP_Enter: puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); if (!(ev->state & ControlMask)) { cleanup(); exit(0); } if (sel) sel->out = 1; break; case XK_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; } if (lines > 0) return; /* fallthrough */ case XK_Down: if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } break; case XK_Tab: if (!sel) return; strncpy(text, sel->text, sizeof text - 1); text[sizeof text - 1] = '\0'; cursor = strlen(text); match(); break; } drawmenu(); } static void paste(void) { char *p, *q; int di; unsigned long dl; Atom da; /* we have been given the current selection, now insert it into input */ XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, utf8, &da, &di, &dl, &dl, (unsigned char **)&p); insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); XFree(p); drawmenu(); } static void readstdin(void) { char buf[sizeof text], *p, *maxstr = NULL; size_t i, max = 0, size = 0; /* read each line from stdin and add it to the item list */ for (i = 0; fgets(buf, sizeof buf, stdin); i++) { if (i + 1 >= size / sizeof *items) if (!(items = realloc(items, (size += BUFSIZ)))) die("cannot realloc %u bytes:", size); if ((p = strchr(buf, '\n'))) *p = '\0'; if (!(items[i].text = strdup(buf))) die("cannot strdup %u bytes:", strlen(buf) + 1); items[i].out = 0; if (strlen(items[i].text) > max) max = strlen(maxstr = items[i].text); } if (items) items[i].text = NULL; inputw = maxstr ? TEXTW(maxstr) : 0; lines = MIN(lines, i); } static void run(void) { XEvent ev; while (!XNextEvent(dpy, &ev)) { if (XFilterEvent(&ev, win)) continue; switch(ev.type) { case Expose: if (ev.xexpose.count == 0) drw_map(drw, win, 0, 0, mw, mh); break; case KeyPress: keypress(&ev.xkey); break; case SelectionNotify: if (ev.xselection.property == utf8) paste(); break; case VisibilityNotify: if (ev.xvisibility.state != VisibilityUnobscured) XRaiseWindow(dpy, win); break; } } } static void setup(void) { int x, y; XSetWindowAttributes swa; XIM xim; #ifdef XINERAMA XineramaScreenInfo *info; Window w, pw, dw, *dws; XWindowAttributes wa; int a, j, di, n, i = 0, area = 0; unsigned int du; #endif /* init appearance */ scheme[SchemeNorm].bg = drw_clr_create(drw, normbgcolor); 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); utf8 = XInternAtom(dpy, "UTF8_STRING", False); /* calculate menu geometry */ bh = drw->fonts[0]->h + 2; lines = MAX(lines, 0); mh = (lines + 1) * bh; #ifdef XINERAMA if ((info = XineramaQueryScreens(dpy, &n))) { XGetInputFocus(dpy, &w, &di); if (mon != -1 && mon < n) i = mon; else if (w != root && w != PointerRoot && w != None) { /* find top-level window containing current input focus */ do { if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) XFree(dws); } while (w != root && w != pw); /* find xinerama screen with which the window intersects most */ if (XGetWindowAttributes(dpy, pw, &wa)) for (j = 0; j < n; j++) if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { area = a; i = j; } } /* no focused window is on screen, so use pointer location instead */ if (mon == -1 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) for (i = 0; i < n; i++) if (INTERSECT(x, y, 1, 1, info[i])) break; x = info[i].x_org; y = info[i].y_org + (topbar ? 0 : info[i].height - mh); mw = info[i].width; XFree(info); } else #endif { x = 0; y = topbar ? 0 : sh - mh; mw = sw; } promptw = (prompt && *prompt) ? TEXTW(prompt) : 0; inputw = MIN(inputw, mw/3); match(); /* create menu window */ swa.override_redirect = True; swa.background_pixel = scheme[SchemeNorm].bg->pix; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; win = XCreateWindow(dpy, root, x, y, mw, mh, 0, DefaultDepth(dpy, screen), CopyFromParent, DefaultVisual(dpy, screen), CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); /* open input methods */ xim = XOpenIM(dpy, NULL, NULL, NULL); xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, XNFocusWindow, win, NULL); XMapRaised(dpy, win); drw_resize(drw, mw, mh); drawmenu(); } static void usage(void) { fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" " [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); exit(1); } int main(int argc, char *argv[]) { int i, fast = 0; for (i = 1; i < argc; i++) /* these options take no arguments */ if (!strcmp(argv[i], "-v")) { /* prints version information */ puts("dmenu-"VERSION); exit(0); } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ topbar = 0; else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ fast = 1; else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; } else if (i + 1 == argc) usage(); /* these options take one argument */ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ lines = atoi(argv[++i]); else if (!strcmp(argv[i], "-m")) mon = atoi(argv[++i]); else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ prompt = argv[++i]; else if (!strcmp(argv[i], "-fn")) /* font or font set */ fonts[0] = argv[++i]; else if (!strcmp(argv[i], "-nb")) /* normal background color */ normbgcolor = argv[++i]; else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ normfgcolor = argv[++i]; else if (!strcmp(argv[i], "-sb")) /* selected background color */ selbgcolor = argv[++i]; else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ selfgcolor = argv[++i]; else usage(); if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); if (!(dpy = XOpenDisplay(NULL))) die("cannot open display\n"); screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); sw = DisplayWidth(dpy, screen); sh = DisplayHeight(dpy, screen); drw = drw_create(dpy, screen, root, sw, sh); drw_load_fonts(drw, fonts, LENGTH(fonts)); if (!drw->fontcount) die("no fonts could be loaded.\n"); drw_setscheme(drw, &scheme[SchemeNorm]); if (fast) { grabkeyboard(); readstdin(); } else { readstdin(); grabkeyboard(); } setup(); run(); return 1; /* unreachable */ } dmenu-4.6/stest.c010064400017500001750000000062351261774771500134350ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include "arg.h" char *argv0; #define FLAG(x) (flag[(x)-'a']) static void test(const char *, const char *); static void usage(void); static int match = 0; static int flag[26]; static struct stat old, new; static void test(const char *path, const char *name) { struct stat st, ln; if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ && (!FLAG('s') || st.st_size > 0) /* not empty */ && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ if (FLAG('q')) exit(0); match = 1; puts(name); } } static void usage(void) { fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " "[-n file] [-o file] [file...]\n", argv0); exit(2); /* like test(1) return > 1 on error */ } int main(int argc, char *argv[]) { struct dirent *d; char path[PATH_MAX], *line = NULL, *file; size_t linesiz = 0; ssize_t n; DIR *dir; int r; ARGBEGIN { case 'n': /* newer than file */ case 'o': /* older than file */ file = EARGF(usage()); if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) perror(file); break; default: /* miscellaneous operators */ if (strchr("abcdefghlpqrsuvwx", ARGC())) FLAG(ARGC()) = 1; else usage(); /* unknown flag */ } ARGEND; if (!argc) { /* read list from stdin */ while ((n = getline(&line, &linesiz, stdin)) > 0) { if (n && line[n - 1] == '\n') line[n - 1] = '\0'; test(line, line); } free(line); } else { for (; argc; argc--, argv++) { if (FLAG('l') && (dir = opendir(*argv))) { /* test directory contents */ while ((d = readdir(dir))) { r = snprintf(path, sizeof path, "%s/%s", *argv, d->d_name); if (r >= 0 && (size_t)r < sizeof path) test(path, d->d_name); } closedir(dir); } else { test(*argv, *argv); } } } return match ? 0 : 1; } dmenu-4.6/util.c010064400017500001750000000007421261774771500132450ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ #include #include #include #include #include "util.h" void * ecalloc(size_t nmemb, size_t size) { void *p; if (!(p = calloc(nmemb, size))) perror(NULL); 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); }