Added border, center, fuzzymatch, incremental, initialtext, instant, line-height, mouse-support, navhistory, non-blocking-stdin, password, pipeout, printinputtext, rejectnomatch, scroll, vertfull, wmtype and xyw patches

This commit is contained in:
bakkeby 2019-09-19 00:33:15 +02:00
commit f128efa1b2
29 changed files with 3179 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
config.h

30
LICENSE Normal file
View File

@ -0,0 +1,30 @@
MIT/X Consortium License
© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
© 2006-2007 Michał Janeczek <janeczek@gmail.com>
© 2007 Kris Maglione <jg@suckless.org>
© 2009 Gottox <gottox@s01.de>
© 2009 Markus Schnalke <meillo@marmaro.de>
© 2009 Evan Gates <evan.gates@gmail.com>
© 2010-2012 Connor Lane Smith <cls@lubutu.com>
© 2014-2019 Hiltjo Posthuma <hiltjo@codemadness.org>
© 2015-2019 Quentin Rameau <quinq@fifth.space>
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.

64
Makefile Normal file
View File

@ -0,0 +1,64 @@
# 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:
$(CC) -c $(CFLAGS) $<
config.h:
cp config.def.h $@
$(OBJ): arg.h config.h config.mk drw.h
dmenu: dmenu.o drw.o util.o
$(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS)
stest: stest.o
$(CC) -o $@ stest.o $(LDFLAGS)
clean:
rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz
dist: clean
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
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
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:
rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\
$(DESTDIR)$(PREFIX)/bin/dmenu_path\
$(DESTDIR)$(PREFIX)/bin/dmenu_run\
$(DESTDIR)$(PREFIX)/bin/stest\
$(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
$(DESTDIR)$(MANPREFIX)/man1/stest.1
.PHONY: all options clean dist install uninstall

24
README Normal file
View File

@ -0,0 +1,24 @@
dmenu - 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.

80
README.md Normal file
View File

@ -0,0 +1,80 @@
Similar to [dwm-flexipatch](https://github.com/bakkeby/dwm-flexipatch) this dmenu 4.9 project has a different take on patching. It uses preprocessor directives to decide whether or not to include a patch during build time. Essentially this means that this build, for better or worse, contains both the patched _and_ the original code. The aim being that you can select which patches to include and the build will contain that code and nothing more.
For example to include the `border` patch then you would only need to flip this setting from 0 to 1 in [patches.h](https://github.com/bakkeby/st-flexipatch/blob/master/patches.h):
```c
#define BORDER_PATCH 1
```
Refer to [https://tools.suckless.org/dmenu/](https://tools.suckless.org/dmenu/) for details on dmenu, how to install it and how it works.
---
### Changelog:
2019-09-18 - Initial commit, added border, center, fuzzymatch, incremental, initialtext, instant, line-height, mouse-support, navhistory, non-blocking-stdin, password, pipeout, printinputtext, rejectnomatch, scroll, vertfull, wmtype and xyw patches
### Patches included:
- [border](http://tools.suckless.org/dmenu/patches/border/)
- adds a border around the dmenu window
- [center](https://tools.suckless.org/dmenu/patches/center/)
- this patch centers dmenu in the middle of the screen
- [fuzzymatch](https://tools.suckless.org/dmenu/patches/fuzzymatch/)
- adds support for fuzzy-matching to dmenu, allowing users to type non-consecutive portions of the string to be matched
- [incremental](https://tools.suckless.org/dmenu/patches/incremental/)
- this patch causes dmenu to print out the current text each time a key is pressed
- [initialtext](https://tools.suckless.org/dmenu/patches/initialtext/)
- adds an option to provide preselected text
- [instant](https://tools.suckless.org/dmenu/patches/instant/)
- adds a flag that will cause dmenu to select an item immediately if there is only one matching option left
- [line-height](http://tools.suckless.org/dmenu/patches/line-height/)
- adds a '-h' option which sets the minimum height of a dmenu line
- this helps integrate dmenu with other UI elements that require a particular vertical size
- [mouse-support](https://tools.suckless.org/dmenu/patches/mouse-support/)
- adds basic mouse support for dmenu
- [navhistory](https://tools.suckless.org/dmenu/patches/navhistory/)
- provides dmenu the ability for history navigation similar to that of bash
- [non-blocking-stdin](https://tools.suckless.org/dmenu/patches/non_blocking_stdin/)
- this is a patch to have dmenu read stdin in a non blocking way, making it wait for input both from stdin and from X
- this means that you can continue feeding dmenu while you type
- the patch is meant to be used along with the incremental patch in order to use stdout to feed stdin
- [password](https://tools.suckless.org/dmenu/patches/password/)
- with this patch dmenu will not directly display the keyboard input, but instead replace it with dots
- all data from stdin will be ignored
- [pipeout](https://tools.suckless.org/dmenu/patches/pipeout/)
- this patch allows the selected text to be piped back out with dmenu
- this can be useful if you want to display the output of a command on the screen
- [prefix-completion](https://tools.suckless.org/dmenu/patches/prefix-completion/)
- changes the behaviour of matched items and the Tab key to allow tab completion
- [printinputtext](https://tools.suckless.org/dmenu/patches/printinputtext/)
- this patch adds a flag (-t) which makes Return key ignore selection and print the input text to stdout
- the flag basically swaps the functions of Return and Shift+Return hotkeys
- [rejectnomatch](https://tools.suckless.org/dmenu/patches/reject-no-match/)
- adds a new flag to dmenu with which text input will be rejected if it would result in no matching item
- [scroll](https://tools.suckless.org/dmenu/patches/scroll/)
- this patch adds support for text scrolling
- it doesn't append '...' for long input anymore as it can handle long text
- [vertfull](https://tools.suckless.org/dmenu/patches/vertfull/)
- prevents dmenu from indenting items at the same level as the prompt length
- [wmtype](https://github.com/Baitinq/dmenu/blob/master/patches/dmenu-wm_type.diff)
- adds extended window manager hints such as \_NET_WM_WINDOW_TYPE and \_NET_WM_WINDOW_TYPE_DOCK
- [xyw](https://tools.suckless.org/dmenu/patches/xyw/)
- adds options for specifying dmenu window position and width

49
arg.h Normal file
View File

@ -0,0 +1,49 @@
/*
* 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][0] == '-'\
&& argv[0][1];\
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

57
config.def.h Normal file
View File

@ -0,0 +1,57 @@
/* 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 */
#if FUZZYMATCH_PATCH
static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
#endif // FUZZYMATCH_PATCH
#if INCREMENTAL_PATCH
static int incremental = 0; /* -r option; if 1, outputs text each time a key is pressed */
#endif // INCREMENTAL_PATCH
#if INSTANT_PATCH
static int instant = 0; /* -n option; if 1, selects matching item without the need to press enter */
#endif // INSTANT_PATCH
#if CENTER_PATCH
static int center = 1; /* -c option; if 0, dmenu won't be centered on the screen */
#endif // CENTER_PATCH
/* -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 left of input field */
static const char *colors[][2] = {
/* fg bg */
[SchemeNorm] = { "#bbbbbb", "#222222" },
[SchemeSel] = { "#eeeeee", "#005577" },
[SchemeOut] = { "#000000", "#00ffff" },
};
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lines = 0;
#if LINE_HEIGHT_PATCH
static unsigned int lineheight = 0; /* -h option; minimum height of a menu line */
#endif // LINE_HEIGHT_PATCH
#if NAVHISTORY_PATCH
static unsigned int maxhist = 15;
static int histnodup = 1; /* if 0, record repeated histories */
#endif // NAVHISTORY_PATCH
/*
* Characters not considered part of a word while deleting words
* for example: " /?\"&[]"
*/
#if PIPEOUT_PATCH
static const char startpipe[] = "#";
#endif // PIPEOUT_PATCH
static const char worddelimiters[] = " ";
#if BORDER_PATCH
/* Size of the window border */
static const unsigned int border_width = 5;
#endif // BORDER_PATCH
#if PREFIXCOMPLETION_PATCH
/*
* Use prefix matching by default; can be inverted with the -x flag.
*/
static int use_prefix = 1;
#endif // PREFIXCOMPLETION_PATCH

31
config.mk Normal file
View File

@ -0,0 +1,31 @@
# dmenu version
VERSION = 4.9
# 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) -lm -lXrender
# flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS)
LDFLAGS = $(LIBS)
# compiler and linker
CC = cc

194
dmenu.1 Normal file
View File

@ -0,0 +1,194 @@
.TH DMENU 1 dmenu\-VERSION
.SH NAME
dmenu \- dynamic menu
.SH SYNOPSIS
.B dmenu
.RB [ \-bfiv ]
.RB [ \-l
.IR lines ]
.RB [ \-m
.IR monitor ]
.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 [ \-w
.IR windowid ]
.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 if not reading from a tty. 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.
.TP
.BI \-w " windowid"
embed into windowid.
.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
.B Ctrl-Left
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
.TP
.B C\-b
Left
.TP
.B C\-c
Escape
.TP
.B C\-d
Delete
.TP
.B C\-e
End
.TP
.B C\-f
Right
.TP
.B C\-g
Escape
.TP
.B C\-h
Backspace
.TP
.B C\-i
Tab
.TP
.B C\-j
Return
.TP
.B C\-J
Shift-Return
.TP
.B C\-k
Delete line right
.TP
.B C\-m
Return
.TP
.B C\-M
Shift-Return
.TP
.B C\-n
Down
.TP
.B C\-p
Up
.TP
.B C\-u
Delete line left
.TP
.B C\-w
Delete word left
.TP
.B C\-y
Paste from primary X selection
.TP
.B C\-Y
Paste from X clipboard
.TP
.B M\-b
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
.TP
.B M\-G
End
.TP
.B M\-h
Up
.TP
.B M\-j
Page down
.TP
.B M\-k
Page up
.TP
.B M\-l
Down
.SH SEE ALSO
.IR dwm (1),
.IR stest (1)

1217
dmenu.c Normal file
View File

@ -0,0 +1,1217 @@
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/Xft/Xft.h>
#include "patches.h"
#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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
/* enums */
enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
struct item {
char *text;
struct item *left, *right;
#if NON_BLOCKING_STDIN_PATCH
struct item *next;
#endif // NON_BLOCKING_STDIN_PATCH
int out;
#if FUZZYMATCH_PATCH
double distance;
#endif // FUZZYMATCH_PATCH
};
static char text[BUFSIZ] = "";
#if PIPEOUT_PATCH
static char pipeout[8] = " | dmenu";
#endif // PIPEOUT_PATCH
static char *embed;
static int bh, mw, mh;
#if XYW_PATCH
static int dmx = 0, dmy = 0; /* put dmenu at these x and y offsets */
static unsigned int dmw = 0; /* make dmenu this wide */
#endif // XYW_PATCH
static int inputw = 0, promptw;
#if PASSWORD_PATCH
static int passwd = 0;
#endif // PASSWORD_PATCH
static int lrpad; /* sum of left and right padding */
#if REJECTNOMATCH_PATCH
static int reject_no_match = 0;
#endif // REJECTNOMATCH_PATCH
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;
#if PRINTINPUTTEXT_PATCH
static int use_text_input = 0;
#endif // PRINTINPUTTEXT_PATCH
static Atom clip, utf8;
#if WMTYPE_PATCH
static Atom type, dock;
#endif // WMTYPE_PATCH
static Display *dpy;
static Window root, parentwin, win;
static XIC xic;
static Drw *drw;
static Clr *scheme[SchemeLast];
#include "patch/include.h"
#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);
static void calcoffsets(void);
static void cleanup(void);
static char * cistrstr(const char *s, const char *sub);
static int drawitem(struct item *item, int x, int y, int w);
static void drawmenu(void);
static void grabfocus(void);
static void grabkeyboard(void);
static void match(void);
static void insert(const char *str, ssize_t n);
static size_t nextrune(int inc);
static void movewordedge(int dir);
static void keypress(XKeyEvent *ev);
static void paste(void);
static void readstdin(void);
static void run(void);
static void setup(void);
static void usage(void);
#include "patch/include.c"
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++)
free(scheme[i]);
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 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
drawmenu(void)
{
#if SCROLL_PATCH
static int curpos, oldcurlen;
int curlen, rcurlen;
#else
unsigned int curpos;
#endif // SCROLL_PATCH
struct item *item;
int x = 0, y = 0, w;
#if LINE_HEIGHT_PATCH
int fh = drw->fonts->h;
#endif // LINE_HEIGHT_PATCH
#if PASSWORD_PATCH
char *censort;
#endif // PASSWORD_PATCH
drw_setscheme(drw, scheme[SchemeNorm]);
drw_rect(drw, 0, 0, mw, mh, 1, 1);
if (prompt && *prompt) {
drw_setscheme(drw, scheme[SchemeSel]);
x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
}
/* draw input field */
w = (lines > 0 || !matches) ? mw - x : inputw;
#if SCROLL_PATCH
w -= lrpad / 2;
x += lrpad / 2;
rcurlen = drw_fontset_getwidth(drw, text + cursor);
curlen = drw_fontset_getwidth(drw, text) - rcurlen;
curpos += curlen - oldcurlen;
curpos = MIN(w, MAX(0, curpos));
curpos = MAX(curpos, w - rcurlen);
curpos = MIN(curpos, curlen);
oldcurlen = curlen;
drw_setscheme(drw, scheme[SchemeNorm]);
#if PASSWORD_PATCH
if (passwd) {
censort = ecalloc(1, sizeof(text));
memset(censort, '.', strlen(text));
drw_text_align(drw, x, 0, curpos, bh, censort, cursor, AlignR);
drw_text_align(drw, x + curpos, 0, w - curpos, bh, censort + cursor, strlen(censort) - cursor, AlignL);
free(censort);
} else {
drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR);
drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL);
}
#else
drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR);
drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL);
#endif // PASSWORD_PATCH
#if LINE_HEIGHT_PATCH
drw_rect(drw, x + curpos - 1, 2 + (bh-fh)/2, 2, fh - 4, 1, 0);
#else
drw_rect(drw, x + curpos - 1, 2, 2, bh - 4, 1, 0);
#endif // LINE_HEIGHT_PATCH
#else // !SCROLL_PATCH
drw_setscheme(drw, scheme[SchemeNorm]);
#if PASSWORD_PATCH
if (passwd) {
censort = ecalloc(1, sizeof(text));
memset(censort, '.', strlen(text));
drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0);
free(censort);
} else
drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
#else
drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
#endif // PASSWORD_PATCH
curpos = TEXTW(text) - TEXTW(&text[cursor]);
if ((curpos += lrpad / 2 - 1) < w) {
drw_setscheme(drw, scheme[SchemeNorm]);
#if LINE_HEIGHT_PATCH
drw_rect(drw, x + curpos, 2 + (bh-fh)/2, 2, fh - 4, 1, 0);
#else
drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
#endif // LINE_HEIGHT_PATCH
}
#endif // SCROLL_PATCH
if (lines > 0) {
/* draw vertical list */
for (item = curr; item != next; item = item->right)
#if VERTFULL_PATCH
drawitem(item, 0, y += bh, mw);
#else
drawitem(item, x, y += bh, mw - x);
#endif // VERTFULL_PATCH
} 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, lrpad / 2, "<", 0);
}
x += w;
for (item = curr; item != next; item = item->right)
x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">")));
if (next) {
w = TEXTW(">");
drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
}
}
drw_map(drw, win, 0, 0, mw, mh);
#if NON_BLOCKING_STDIN_PATCH
XFlush(dpy);
#endif // NON_BLOCKING_STDIN_PATCH
}
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
grabkeyboard(void)
{
struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
int i;
if (embed)
return;
/* 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");
}
static void
match(void)
{
#if FUZZYMATCH_PATCH
if (fuzzy) {
fuzzymatch();
return;
}
#endif
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;
#if NON_BLOCKING_STDIN_PATCH
int preserve = 0;
#endif // NON_BLOCKING_STDIN_PATCH
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:", tokn * sizeof *tokv);
len = tokc ? strlen(tokv[0]) : 0;
#if PREFIXCOMPLETION_PATCH
if (use_prefix) {
matches = lprefix = matchend = prefixend = NULL;
textsize = strlen(text);
} else {
matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
textsize = strlen(text) + 1;
}
#else
matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
textsize = strlen(text) + 1;
#endif // PREFIXCOMPLETION_PATCH
#if NON_BLOCKING_STDIN_PATCH
for (item = items; item; item = item->next)
#else
for (item = items; item && item->text; item++)
#endif
{
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);
#if PREFIXCOMPLETION_PATCH
else if (!use_prefix)
#else
else
#endif // PREFIXCOMPLETION_PATCH
appenditem(item, &lsubstr, &substrend);
#if NON_BLOCKING_STDIN_PATCH
if (sel == item)
preserve = 1;
#endif // NON_BLOCKING_STDIN_PATCH
}
if (lprefix) {
if (matches) {
matchend->right = lprefix;
lprefix->left = matchend;
} else
matches = lprefix;
matchend = prefixend;
}
#if PREFIXCOMPLETION_PATCH
if (!use_prefix && lsubstr)
#else
if (lsubstr)
#endif // PREFIXCOMPLETION_PATCH
{
if (matches) {
matchend->right = lsubstr;
lsubstr->left = matchend;
} else
matches = lsubstr;
matchend = substrend;
}
#if NON_BLOCKING_STDIN_PATCH
if (!preserve)
#endif // NON_BLOCKING_STDIN_PATCH
curr = sel = matches;
#if INSTANT_PATCH
if (instant && matches && matches==matchend && !lsubstr) {
puts(matches->text);
cleanup();
exit(0);
}
#endif // INSTANT_PATCH
calcoffsets();
}
static void
insert(const char *str, ssize_t n)
{
if (strlen(text) + n > sizeof text - 1)
return;
#if REJECTNOMATCH_PATCH
static char last[BUFSIZ] = "";
if (reject_no_match) {
/* store last text value in case we need to revert it */
memcpy(last, text, BUFSIZ);
}
#endif // REJECTNOMATCH_PATCH
/* 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();
#if REJECTNOMATCH_PATCH
if (!matches && reject_no_match) {
/* revert to last text value if theres no match */
memcpy(text, last, BUFSIZ);
cursor -= n;
match();
}
#endif // REJECTNOMATCH_PATCH
}
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
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
keypress(XKeyEvent *ev)
{
char buf[32];
int len;
#if PREFIXCOMPLETION_PATCH
struct item * item;
#endif // PREFIXCOMPLETION_PATCH
KeySym ksym;
Status status;
len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
switch (status) {
default: /* XLookupNone, XBufferOverflow */
return;
case XLookupChars:
goto insert;
case XLookupKeySym:
case XLookupBoth:
break;
}
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 && strchr(worddelimiters, text[nextrune(-1)]))
insert(NULL, nextrune(-1) - cursor);
while (cursor > 0 && !strchr(worddelimiters, 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_Left:
movewordedge(-1);
goto draw;
case XK_Right:
movewordedge(+1);
goto draw;
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_b:
movewordedge(-1);
goto draw;
case XK_f:
movewordedge(+1);
goto draw;
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;
#if NAVHISTORY_PATCH
case XK_p: navhistory(-1); buf[0]=0; break;
case XK_n: navhistory(1); buf[0]=0; break;
#endif // NAVHISTORY_PATCH
default:
return;
}
}
switch(ksym) {
default:
insert:
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:
#if PIPEOUT_PATCH
#if PRINTINPUTTEXT_PATCH
if (sel && (
(use_text_input && (ev->state & ShiftMask)) ||
(!use_text_input && !(ev->state & ShiftMask))
))
#else
if (sel && !(ev->state & ShiftMask))
#endif // PRINTINPUTTEXT_PATCH
{
if (sel->text[0] == startpipe[0]) {
strncpy(sel->text + strlen(sel->text),pipeout,8);
puts(sel->text+1);
}
puts(sel->text);
} else {
if (text[0] == startpipe[0]) {
strncpy(text + strlen(text),pipeout,8);
puts(text+1);
}
puts(text);
}
#elif PRINTINPUTTEXT_PATCH
if (use_text_input)
puts((sel && (ev->state & ShiftMask)) ? sel->text : text);
else
puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
#else
puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
#endif // PIPEOUT_PATCH
if (!(ev->state & ControlMask)) {
#if NAVHISTORY_PATCH
savehistory((sel && !(ev->state & ShiftMask))
? sel->text : text);
#endif // NAVHISTORY_PATCH
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 PREFIXCOMPLETION_PATCH
if (!matches) break; /* cannot complete no matches */
strncpy(text, matches->text, sizeof text - 1);
text[sizeof text - 1] = '\0';
len = cursor = strlen(text); /* length of longest common prefix */
for (item = matches; item && item->text; item = item->right) {
cursor = 0;
while (cursor < len && text[cursor] == item->text[cursor])
cursor++;
len = cursor;
}
memset(text + len, '\0', strlen(text) - len);
#else
if (!sel)
return;
strncpy(text, sel->text, sizeof text - 1);
text[sizeof text - 1] = '\0';
cursor = strlen(text);
match();
#endif //
break;
}
draw:
#if INCREMENTAL_PATCH
if (incremental) {
puts(text);
fflush(stdout);
}
#endif // INCREMENTAL_PATCH
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 */
if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
== Success && p) {
insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
XFree(p);
}
drawmenu();
}
#if !NON_BLOCKING_STDIN_PATCH
static void
readstdin(void)
{
char buf[sizeof text], *p;
size_t i, imax = 0, size = 0;
unsigned int tmpmax = 0;
#if PASSWORD_PATCH
if (passwd) {
inputw = lines = 0;
return;
}
#endif // PASSWORD_PATCH
/* 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;
drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL);
if (tmpmax > inputw) {
inputw = tmpmax;
imax = i;
}
}
if (items)
items[i].text = NULL;
inputw = items ? TEXTW(items[imax].text) : 0;
lines = MIN(lines, i);
}
#endif // NON_BLOCKING_STDIN_PATCH
static void
#if NON_BLOCKING_STDIN_PATCH
readevent(void)
#else
run(void)
#endif // NON_BLOCKING_STDIN_PATCH
{
XEvent ev;
while (!XNextEvent(dpy, &ev)) {
if (XFilterEvent(&ev, win))
continue;
switch(ev.type) {
#if MOUSE_SUPPORT_PATCH
case ButtonPress:
buttonpress(&ev);
break;
#endif // MOUSE_SUPPORT_PATCH
case DestroyNotify:
if (ev.xdestroywindow.window != win)
break;
cleanup();
exit(1);
case Expose:
if (ev.xexpose.count == 0)
drw_map(drw, win, 0, 0, mw, mh);
break;
case FocusIn:
/* regrab focus from parent window */
if (ev.xfocus.window != win)
grabfocus();
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, i, j;
unsigned int du;
XSetWindowAttributes swa;
XIM xim;
Window w, dw, *dws;
XWindowAttributes wa;
XClassHint ch = {"dmenu", "dmenu"};
#ifdef XINERAMA
XineramaScreenInfo *info;
Window pw;
int a, di, n, area = 0;
#endif
/* init appearance */
for (j = 0; j < SchemeLast; j++)
scheme[j] = drw_scm_create(drw, colors[j], 2);
clip = XInternAtom(dpy, "CLIPBOARD", False);
utf8 = XInternAtom(dpy, "UTF8_STRING", False);
#if WMTYPE_PATCH
type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
#endif // WMTYPE_PATCH
/* calculate menu geometry */
bh = drw->fonts->h + 2;
#if LINE_HEIGHT_PATCH
bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */
#endif // LINE_HEIGHT_PATCH
lines = MAX(lines, 0);
mh = (lines + 1) * bh;
#if CENTER_PATCH
if (center)
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#endif // CENTER_PATCH
#ifdef XINERAMA
i = 0;
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
XGetInputFocus(dpy, &w, &di);
if (mon >= 0 && 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 < 0 && !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;
#if CENTER_PATCH
if (center) {
mw = MIN(MAX(max_textw() + promptw, 100), info[i].width);
x = info[i].x_org + ((info[i].width - mw) / 2);
y = info[i].y_org + ((info[i].height - mh) / 2);
} else {
#if XYW_PATCH
x = info[i].x_org + dmx;
y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy);
mw = (dmw>0 ? dmw : info[i].width);
#else
x = info[i].x_org;
y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
mw = info[i].width;
#endif // XYW_PATCH
}
#elif XYW_PATCH
x = info[i].x_org + dmx;
y = info[i].y_org + (topbar ? dmy : info[i].height - mh - dmy);
mw = (dmw>0 ? dmw : info[i].width);
#else
x = info[i].x_org;
y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
mw = info[i].width;
#endif // CENTER_PATCH / XYW_PATCH
XFree(info);
} else
#endif
{
if (!XGetWindowAttributes(dpy, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
#if CENTER_PATCH
if (center) {
mw = MIN(MAX(max_textw() + promptw, 100), wa.width);
x = (wa.width - mw) / 2;
y = (wa.height - mh) / 2;
} else {
#if XYW_PATCH
x = dmx;
y = topbar ? dmy : wa.height - mh - dmy;
mw = (dmw>0 ? dmw : wa.width);
#else
x = 0;
y = topbar ? 0 : wa.height - mh;
mw = wa.width;
#endif // XYW_PATCH
}
#elif XYW_PATCH
x = dmx;
y = topbar ? dmy : wa.height - mh - dmy;
mw = (dmw>0 ? dmw : wa.width);
#else
x = 0;
y = topbar ? 0 : wa.height - mh;
mw = wa.width;
#endif // CENTER_PATCH / XYW_PATCH
}
#if CENTER_PATCH
if (!center)
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#endif // CENTER_PATCH
inputw = MIN(inputw, mw/3);
match();
/* create menu window */
swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask
#if MOUSE_SUPPORT_PATCH
| ButtonPressMask
#endif // MOUSE_SUPPORT_PATCH
;
#if BORDER_PATCH
win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
#else
win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
#endif // BORDER_PATCH
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
#if BORDER_PATCH
XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
#endif // BORDER_PATCH
XSetClassHint(dpy, win, &ch);
#if WMTYPE_PATCH
XChangeProperty(dpy, win, type, XA_ATOM, 32, PropModeReplace,
(unsigned char *) &dock, 1);
#endif // WMTYPE_PATCH
/* input methods */
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
die("XOpenIM failed: could not open input device");
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL);
XMapRaised(dpy, win);
if (embed) {
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);
drawmenu();
}
static void
usage(void)
{
fputs("usage: dmenu [-bfv"
#if CENTER_PATCH
"c"
#endif
#if !NON_BLOCKING_STDIN_PATCH
"i"
#endif // NON_BLOCKING_STDIN_PATCH
#if INCREMENTAL_PATCH
"r"
#endif // INCREMENTAL_PATCH
#if INSTANT_PATCH
"n"
#endif // INSTANT_PATCH
#if PRINTINPUTTEXT_PATCH
"t"
#endif // PRINTINPUTTEXT_PATCH
#if PREFIXCOMPLETION_PATCH
"x"
#endif // PREFIXCOMPLETION_PATCH
#if FUZZYMATCH_PATCH
"F"
#endif // FUZZYMATCH_PATCH
#if PASSWORD_PATCH
"P"
#endif // PASSWORD_PATCH
#if REJECTNOMATCH_PATCH
"R" // (changed from r to R due to conflict with INCREMENTAL_PATCH)
#endif // REJECTNOMATCH_PATCH
"] [-l lines] [-p prompt] [-fn font] [-m monitor]"
"\n [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"
#if INITIALTEXT_PATCH || LINE_HEIGHT_PATCH
"\n "
#endif
#if INITIALTEXT_PATCH
" [-it text]"
#endif // INITIALTEXT_PATCH
#if LINE_HEIGHT_PATCH
" [-h height]"
#endif // LINE_HEIGHT_PATCH
#if NAVHISTORY_PATCH
" [-H histfile]"
#endif // NAVHISTORY_PATCH
#if XYW_PATCH
" [-X xoffset] [-Y yoffset] [-W width]" // (arguments made upper case due to conflicts)
#endif // XYW_PATCH
"\n", stderr);
exit(1);
}
int
main(int argc, char *argv[])
{
XWindowAttributes wa;
int i;
#if !NON_BLOCKING_STDIN_PATCH
int fast = 0;
#endif // NON_BLOCKING_STDIN_PATCH
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;
#if CENTER_PATCH
} else if (!strcmp(argv[i], "-c")) { /* toggles centering of dmenu window on screen */
center = !center;
#endif
#if !NON_BLOCKING_STDIN_PATCH
} else if (!strcmp(argv[i], "-f")) { /* grabs keyboard before reading stdin */
fast = 1;
#endif // NON_BLOCKING_STDIN_PATCH
#if INCREMENTAL_PATCH
} else if (!strcmp(argv[i], "-r")) { /* incremental */
incremental = !incremental;
#endif // INCREMENTAL_PATCH
} else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
fstrncmp = strncasecmp;
fstrstr = cistrstr;
#if INSTANT_PATCH
} else if (!strcmp(argv[i], "-n")) { /* instant select only match */
instant = !instant;
#endif // INSTANT_PATCH
#if PRINTINPUTTEXT_PATCH
} else if (!strcmp(argv[i], "-t")) { /* favors text input over selection */
use_text_input = 1;
#endif // PRINTINPUTTEXT_PATCH
#if PREFIXCOMPLETION_PATCH
} else if (!strcmp(argv[i], "-x")) { /* invert use_prefix */
use_prefix = !use_prefix;
#endif // PREFIXCOMPLETION_PATCH
#if FUZZYMATCH_PATCH
} else if (!strcmp(argv[i], "-F")) { /* disable/enable fuzzy matching, depends on default */
fuzzy = !fuzzy;
#endif // FUZZYMATCH_PATCH
#if PASSWORD_PATCH
} else if (!strcmp(argv[i], "-P")) { /* is the input a password */
passwd = 1;
#endif // PASSWORD_PATCH
#if REJECTNOMATCH_PATCH
} else if (!strcmp(argv[i], "-R")) { /* reject input which results in no match */
reject_no_match = 1;
#endif // REJECTNOMATCH_PATCH
} else if (i + 1 == argc)
usage();
/* these options take one argument */
#if NAVHISTORY_PATCH
else if (!strcmp(argv[i], "-H"))
histfile = argv[++i];
#endif // NAVHISTORY_PATCH
else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
lines = atoi(argv[++i]);
#if XYW_PATCH
else if (!strcmp(argv[i], "-X")) /* window x offset */
dmx = atoi(argv[++i]);
else if (!strcmp(argv[i], "-Y")) /* window y offset (from bottom up if -b) */
dmy = atoi(argv[++i]);
else if (!strcmp(argv[i], "-W")) /* make dmenu this wide */
dmw = atoi(argv[++i]);
#endif // XYW_PATCH
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];
#if LINE_HEIGHT_PATCH
else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */
lineheight = atoi(argv[++i]);
lineheight = MAX(lineheight,8); /* reasonable default in case of value too small/negative */
}
#endif // LINE_HEIGHT_PATCH
else if (!strcmp(argv[i], "-nb")) /* normal background color */
colors[SchemeNorm][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-nf")) /* normal foreground color */
colors[SchemeNorm][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-sb")) /* selected background color */
colors[SchemeSel][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
colors[SchemeSel][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-w")) /* embedding window id */
embed = argv[++i];
#if INITIALTEXT_PATCH
else if (!strcmp(argv[i], "-it")) { /* adds initial text */
const char * text = argv[++i];
insert(text, strlen(text));
}
#endif // INITIALTEXT_PATCH
else
usage();
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("warning: no locale support\n", stderr);
if (!(dpy = XOpenDisplay(NULL)))
die("cannot open display");
screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen);
if (!embed || !(parentwin = strtol(embed, NULL, 0)))
parentwin = root;
if (!XGetWindowAttributes(dpy, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
drw = drw_create(dpy, screen, root, wa.width, wa.height);
if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
die("no fonts could be loaded.");
lrpad = drw->fonts->h;
#ifdef __OpenBSD__
if (pledge("stdio rpath", NULL) == -1)
die("pledge");
#endif
#if NAVHISTORY_PATCH
loadhistory();
#endif // NAVHISTORY_PATCH
#if NON_BLOCKING_STDIN_PATCH
grabkeyboard();
#else
if (fast && !isatty(0)) {
grabkeyboard();
readstdin();
} else {
readstdin();
grabkeyboard();
}
#endif // NON_BLOCKING_STDIN_PATCH
setup();
run();
return 1; /* unreachable */
}

13
dmenu_path Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh
cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
cache="$cachedir/dmenu_run"
[ ! -e "$cachedir" ] && mkdir -p "$cachedir"
IFS=:
if stest -dqr -n "$cache" $PATH; then
stest -flx $PATH | sort -u | tee "$cache"
else
cat "$cache"
fi

2
dmenu_run Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &

440
drw.c Normal file
View File

@ -0,0 +1,440 @@
/* See LICENSE file for copyright and license details. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include "patches.h"
#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 = 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);
XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
return drw;
}
void
drw_resize(Drw *drw, unsigned int w, unsigned int h)
{
if (!drw)
return;
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)
{
XFreePixmap(drw->dpy, drw->drawable);
XFreeGC(drw->dpy, drw->gc);
free(drw);
}
/* This function is an implementation detail. Library users should use
* drw_fontset_create instead.
*/
static Fnt *
xfont_create(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 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 from name: '%s'\n", fontname);
return NULL;
}
if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
XftFontClose(drw->dpy, xfont);
return NULL;
}
} else if (fontpattern) {
if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
fprintf(stderr, "error, cannot load font from pattern.\n");
return NULL;
}
} else {
die("no font specified.");
}
/* Do not allow using color fonts. This is a workaround for a BadLength
* error from Xft with color glyphs. Modelled on the Xterm workaround. See
* https://bugzilla.redhat.com/show_bug.cgi?id=1498269
* https://lists.suckless.org/dev/1701/30932.html
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
* and lots more all over the internet.
*/
FcBool iscol;
if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
XftFontClose(drw->dpy, xfont);
return NULL;
}
font = ecalloc(1, sizeof(Fnt));
font->xfont = xfont;
font->pattern = pattern;
font->h = xfont->ascent + xfont->descent;
font->dpy = drw->dpy;
return font;
}
static void
xfont_free(Fnt *font)
{
if (!font)
return;
if (font->pattern)
FcPatternDestroy(font->pattern);
XftFontClose(font->dpy, font->xfont);
free(font);
}
Fnt*
drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
{
Fnt *cur, *ret = NULL;
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;
if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen),
clrname, dest))
die("error, cannot allocate color '%s'", clrname);
}
/* Wrapper to create color schemes. The caller has to call free(3) on the
* 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
drw_setfontset(Drw *drw, Fnt *set)
{
if (drw)
drw->fonts = set;
}
void
drw_setscheme(Drw *drw, Clr *scm)
{
if (drw)
drw->scheme = scm;
}
void
drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
{
if (!drw || !drw->scheme)
return;
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
if (filled)
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
else
XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
}
int
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
{
char buf[1024];
int ty;
unsigned int ew;
XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont;
size_t i, len;
int utf8strlen, utf8charlen, render = x || y || w || h;
long utf8codepoint = 0;
const char *utf8str;
FcCharSet *fccharset;
FcPattern *fcpattern;
FcPattern *match;
XftResult result;
int charexists = 0;
if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
return 0;
if (!render) {
w = ~w;
} else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
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));
x += lpad;
w -= lpad;
}
usedfont = drw->fonts;
while (1) {
utf8strlen = 0;
utf8str = text;
nextfont = NULL;
while (*text) {
utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) {
if (curfont == usedfont) {
utf8strlen += utf8charlen;
text += utf8charlen;
} else {
nextfont = curfont;
}
break;
}
}
if (!charexists || nextfont)
break;
else
charexists = 0;
}
if (utf8strlen) {
drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
/* shorten text if necessary */
for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
if (len) {
memcpy(buf, utf8str, len);
buf[len] = '\0';
if (len < utf8strlen)
for (i = len; i && i > len - 3; buf[--i] = '.')
; /* NOP */
if (render) {
ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
usedfont->xfont, x, ty, (XftChar8 *)buf, len);
}
x += ew;
w -= ew;
}
}
if (!*text) {
break;
} else if (nextfont) {
charexists = 0;
usedfont = nextfont;
} else {
/* Regardless of whether or not a fallback font is found, the
* character must be drawn. */
charexists = 1;
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint);
if (!drw->fonts->pattern) {
/* Refer to the comment in xfont_create for more information. */
die("the first font in the cache must be loaded from a font string.");
}
fcpattern = FcPatternDuplicate(drw->fonts->pattern);
FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
FcDefaultSubstitute(fcpattern);
match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
FcCharSetDestroy(fccharset);
FcPatternDestroy(fcpattern);
if (match) {
usedfont = xfont_create(drw, NULL, match);
if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
; /* NOP */
curfont->next = usedfont;
} else {
xfont_free(usedfont);
usedfont = drw->fonts;
}
}
}
}
if (d)
XftDrawDestroy(d);
return x + (render ? w : 0);
}
void
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);
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);
}
void
drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
{
XGlyphInfo ext;
if (!font || !text)
return;
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
if (w)
*w = ext.xOff;
if (h)
*h = font->h;
}
Cur *
drw_cur_create(Drw *drw, int shape)
{
Cur *cur;
if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
return NULL;
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);
}
#if SCROLL_PATCH
#include "patch/scroll.c"
#endif

61
drw.h Normal file
View File

@ -0,0 +1,61 @@
/* See LICENSE file for copyright and license details. */
typedef struct {
Cursor cursor;
} Cur;
typedef struct Fnt {
Display *dpy;
unsigned int h;
XftFont *xfont;
FcPattern *pattern;
struct Fnt *next;
} Fnt;
enum { ColFg, ColBg }; /* Clr scheme index */
typedef XftColor Clr;
typedef struct {
unsigned int w, h;
Display *dpy;
int screen;
Window root;
Drawable drawable;
GC gc;
Clr *scheme;
Fnt *fonts;
} Drw;
/* Drawable abstraction */
Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
void drw_resize(Drw *drw, unsigned int w, unsigned int h);
void drw_free(Drw *drw);
/* Fnt abstraction */
Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
void drw_fontset_free(Fnt* set);
unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
/* Colorscheme abstraction */
void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
/* Cursor abstraction */
Cur *drw_cur_create(Drw *drw, int shape);
void drw_cur_free(Drw *drw, Cur *cursor);
/* Drawing context manipulation */
void drw_setfontset(Drw *drw, Fnt *set);
void drw_setscheme(Drw *drw, Clr *scm);
/* Drawing functions */
void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
/* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
#if SCROLL_PATCH
#include "patch/scroll.h"
#endif

8
patch/center.c Normal file
View File

@ -0,0 +1,8 @@
static int
max_textw(void)
{
int len = 0;
for (struct item *item = items; item && item->text; item++)
len = MAX(TEXTW(item->text), len);
return len;
}

82
patch/fuzzymatch.c Normal file
View File

@ -0,0 +1,82 @@
#include <math.h>
int
compare_distance(const void *a, const void *b)
{
struct item *da = *(struct item **) a;
struct item *db = *(struct item **) b;
if (!db)
return 1;
if (!da)
return -1;
return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
}
void
fuzzymatch(void)
{
/* bang - we have so much memory */
struct item *it;
struct item **fuzzymatches = NULL;
char c;
int number_of_matches = 0, i, pidx, sidx, eidx;
int text_len = strlen(text), itext_len;
matches = matchend = NULL;
/* walk through all items */
for (it = items; it && it->text; it++) {
if (text_len) {
itext_len = strlen(it->text);
pidx = 0; /* pointer */
sidx = eidx = -1; /* start of match, end of match */
/* walk through item text */
for (i = 0; i < itext_len && (c = it->text[i]); i++) {
/* fuzzy match pattern */
if (!fstrncmp(&text[pidx], &c, 1)) {
if(sidx == -1)
sidx = i;
pidx++;
if (pidx == text_len) {
eidx = i;
break;
}
}
}
/* build list of matches */
if (eidx != -1) {
/* compute distance */
/* add penalty if match starts late (log(sidx+2))
* add penalty for long a match without many matching characters */
it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
/* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
appenditem(it, &matches, &matchend);
number_of_matches++;
}
} else {
appenditem(it, &matches, &matchend);
}
}
if (number_of_matches) {
/* initialize array with matches */
if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
fuzzymatches[i] = it;
}
/* sort matches according to distance */
qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
/* rebuild list of matches */
matches = matchend = NULL;
for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \
it->text; i++, it = fuzzymatches[i]) {
appenditem(it, &matches, &matchend);
}
free(fuzzymatches);
}
curr = sel = matches;
calcoffsets();
}

19
patch/include.c Normal file
View File

@ -0,0 +1,19 @@
#if CENTER_PATCH
#include "center.c"
#endif
#if FUZZYMATCH_PATCH
#include "fuzzymatch.c"
#endif
#if MOUSE_SUPPORT_PATCH
#include "mousesupport.c"
#endif
#if NAVHISTORY_PATCH
#include "navhistory.c"
#endif
#if NON_BLOCKING_STDIN_PATCH
#include "nonblockingstdin.c"
#endif

3
patch/include.h Normal file
View File

@ -0,0 +1,3 @@
#if NON_BLOCKING_STDIN_PATCH
#include "nonblockingstdin.h"
#endif

112
patch/mousesupport.c Normal file
View File

@ -0,0 +1,112 @@
static void
buttonpress(XEvent *e)
{
struct item *item;
XButtonPressedEvent *ev = &e->xbutton;
int x = 0, y = 0, h = bh, w;
if (ev->window != win)
return;
/* right-click: exit */
if (ev->button == Button3)
exit(1);
if (prompt && *prompt)
x += promptw;
/* input field */
w = (lines > 0 || !matches) ? mw - x : inputw;
/* left-click on input: clear input,
* NOTE: if there is no left-arrow the space for < is reserved so
* add that to the input width */
if (ev->button == Button1 &&
((lines <= 0 && ev->x >= 0 && ev->x <= x + w +
((!prev || !curr->left) ? TEXTW("<") : 0)) ||
(lines > 0 && ev->y >= y && ev->y <= y + h))) {
insert(NULL, -cursor);
drawmenu();
return;
}
/* middle-mouse click: paste selection */
if (ev->button == Button2) {
XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
utf8, utf8, win, CurrentTime);
drawmenu();
return;
}
/* scroll up */
if (ev->button == Button4 && prev) {
sel = curr = prev;
calcoffsets();
drawmenu();
return;
}
/* scroll down */
if (ev->button == Button5 && next) {
sel = curr = next;
calcoffsets();
drawmenu();
return;
}
if (ev->button != Button1)
return;
if (ev->state & ~ControlMask)
return;
if (lines > 0) {
/* vertical list: (ctrl)left-click on item */
w = mw - x;
for (item = curr; item != next; item = item->right) {
y += h;
if (ev->y >= y && ev->y <= (y + h)) {
puts(item->text);
if (!(ev->state & ControlMask))
exit(0);
sel = item;
if (sel) {
sel->out = 1;
drawmenu();
}
return;
}
}
} else if (matches) {
/* left-click on left arrow */
x += inputw;
w = TEXTW("<");
if (prev && curr->left) {
if (ev->x >= x && ev->x <= x + w) {
sel = curr = prev;
calcoffsets();
drawmenu();
return;
}
}
/* horizontal list: (ctrl)left-click on item */
for (item = curr; item != next; item = item->right) {
x += w;
w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
if (ev->x >= x && ev->x <= x + w) {
puts(item->text);
if (!(ev->state & ControlMask))
exit(0);
sel = item;
if (sel) {
sel->out = 1;
drawmenu();
}
return;
}
}
/* left-click on right arrow */
w = TEXTW(">");
x = mw - w;
if (next && ev->x >= x && ev->x <= x + w) {
sel = curr = next;
calcoffsets();
drawmenu();
return;
}
}
}

103
patch/navhistory.c Normal file
View File

@ -0,0 +1,103 @@
static char *histfile;
static char *histbuf, *histptr;
static size_t histsz;
static void
loadhistory(void)
{
FILE *fp = NULL;
size_t sz;
if (!histfile)
return;
if (!(fp = fopen(histfile, "r")))
return;
fseek(fp, 0, SEEK_END);
sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (sz) {
histsz = sz + 1 + BUFSIZ;
if (!(histbuf = malloc(histsz))) {
fprintf(stderr, "warning: cannot malloc %lu "\
"bytes", histsz);
} else {
histptr = histbuf + fread(histbuf, 1, sz, fp);
if (histptr <= histbuf) { /* fread error */
free(histbuf);
histbuf = NULL;
return;
}
if (histptr[-1] != '\n')
*histptr++ = '\n';
histptr[BUFSIZ - 1] = '\0';
*histptr = '\0';
histsz = histptr - histbuf + BUFSIZ;
}
}
fclose(fp);
}
static void
navhistory(int dir)
{
char *p;
size_t len = 0, textlen;
if (!histbuf)
return;
if (dir > 0) {
if (histptr == histbuf + histsz - BUFSIZ)
return;
while (*histptr && *histptr++ != '\n');
for (p = histptr; *p && *p++ != '\n'; len++);
} else {
if (histptr == histbuf)
return;
if (histptr == histbuf + histsz - BUFSIZ) {
textlen = strlen(text);
textlen = MIN(textlen, BUFSIZ - 1);
strncpy(histptr, text, textlen);
histptr[textlen] = '\0';
}
for (histptr--; histptr != histbuf && histptr[-1] != '\n';
histptr--, len++);
}
len = MIN(len, BUFSIZ - 1);
strncpy(text, histptr, len);
text[len] = '\0';
cursor = len;
match();
}
static void
savehistory(char *str)
{
unsigned int n = 0, len = 0;
size_t slen;
char *p;
FILE *fp;
if (!histfile || !maxhist)
return;
if (!(slen = strlen(str)))
return;
if (histbuf && maxhist > 1) {
p = histbuf + histsz - BUFSIZ - 1; /* skip the last newline */
if (histnodup) {
for (; p != histbuf && p[-1] != '\n'; p--, len++);
n++;
if (slen == len && !strncmp(p, str, len)) {
return;
}
}
for (; p != histbuf; p--, len++)
if (p[-1] == '\n' && ++n + 1 > maxhist)
break;
fp = fopen(histfile, "w");
fwrite(p, 1, len + 1, fp); /* plus the last newline */
} else {
fp = fopen(histfile, "w");
}
fwrite(str, 1, strlen(str), fp);
fclose(fp);
}

64
patch/nonblockingstdin.c Normal file
View File

@ -0,0 +1,64 @@
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
static void
readstdin(void)
{
static size_t max = 0;
static struct item **end = &items;
char buf[sizeof text], *p, *maxstr;
struct item *item;
#if PASSWORD_PATCH
if (passwd) {
inputw = lines = 0;
return;
}
#endif // PASSWORD_PATCH
/* read each line from stdin and add it to the item list */
while (fgets(buf, sizeof buf, stdin)) {
if (!(item = malloc(sizeof *item)))
die("cannot malloc %u bytes:", sizeof *item);
if ((p = strchr(buf, '\n')))
*p = '\0';
if (!(item->text = strdup(buf)))
die("cannot strdup %u bytes:", strlen(buf)+1);
if (strlen(item->text) > max) {
max = strlen(maxstr = item->text);
inputw = maxstr ? TEXTW(maxstr) : 0;
}
*end = item;
end = &item->next;
item->next = NULL;
item->out = 0;
}
match();
drawmenu();
}
static void
run(void)
{
fd_set fds;
int flags, xfd = XConnectionNumber(dpy);
if ((flags = fcntl(0, F_GETFL)) == -1)
die("cannot get stdin control flags:");
if (fcntl(0, F_SETFL, flags | O_NONBLOCK) == -1)
die("cannot set stdin control flags:");
for (;;) {
FD_ZERO(&fds);
FD_SET(xfd, &fds);
if (!feof(stdin))
FD_SET(0, &fds);
if (select(xfd + 1, &fds, NULL, NULL, NULL) == -1)
die("cannot multiplex input:");
if (FD_ISSET(xfd, &fds))
readevent();
if (FD_ISSET(0, &fds))
readstdin();
}
}

1
patch/nonblockingstdin.h Normal file
View File

@ -0,0 +1 @@
static void readevent();

168
patch/scroll.c Normal file
View File

@ -0,0 +1,168 @@
int
utf8nextchar(const char *str, int len, int i, int inc)
{
int n;
for (n = i + inc; n + inc >= 0 && n + inc <= len
&& (str[n] & 0xc0) == 0x80; n += inc)
;
return n;
}
int
drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align)
{
int ty;
unsigned int ew;
XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont;
size_t len;
int utf8strlen, utf8charlen, render = x || y || w || h;
long utf8codepoint = 0;
const char *utf8str;
FcCharSet *fccharset;
FcPattern *fcpattern;
FcPattern *match;
XftResult result;
int charexists = 0;
int i, n;
if (!drw || (render && !drw->scheme) || !text || !drw->fonts || textlen <= 0
|| (align != AlignL && align != AlignR))
return 0;
if (!render) {
w = ~w;
} else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
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));
}
usedfont = drw->fonts;
i = align == AlignL ? 0 : textlen;
x = align == AlignL ? x : x + w;
while (1) {
utf8strlen = 0;
nextfont = NULL;
/* if (align == AlignL) */
utf8str = text + i;
while ((align == AlignL && i < textlen) || (align == AlignR && i > 0)) {
if (align == AlignL) {
utf8charlen = utf8decode(text + i, &utf8codepoint, MIN(textlen - i, UTF_SIZ));
if (!utf8charlen) {
textlen = i;
break;
}
} else {
n = utf8nextchar(text, textlen, i, -1);
utf8charlen = utf8decode(text + n, &utf8codepoint, MIN(textlen - n, UTF_SIZ));
if (!utf8charlen) {
textlen -= i;
text += i;
i = 0;
break;
}
}
for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) {
if (curfont == usedfont) {
utf8strlen += utf8charlen;
i += align == AlignL ? utf8charlen : -utf8charlen;
} else {
nextfont = curfont;
}
break;
}
}
if (!charexists || nextfont)
break;
else
charexists = 0;
}
if (align == AlignR)
utf8str = text + i;
if (utf8strlen) {
drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
/* shorten text if necessary */
if (align == AlignL) {
for (len = utf8strlen; len && ew > w; ) {
len = utf8nextchar(utf8str, len, len, -1);
drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
}
} else {
for (len = utf8strlen; len && ew > w; ) {
n = utf8nextchar(utf8str, len, 0, +1);
utf8str += n;
len -= n;
drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
}
}
if (len) {
if (render) {
ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
XftDrawStringUtf8(d, &drw->scheme[ColFg],
usedfont->xfont, align == AlignL ? x : x - ew, ty, (XftChar8 *)utf8str, len);
}
x += align == AlignL ? ew : -ew;
w -= ew;
}
if (len < utf8strlen)
break;
}
if ((align == AlignR && i <= 0) || (align == AlignL && i >= textlen)) {
break;
} else if (nextfont) {
charexists = 0;
usedfont = nextfont;
} else {
/* Regardless of whether or not a fallback font is found, the
* character must be drawn. */
charexists = 1;
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint);
if (!drw->fonts->pattern) {
/* Refer to the comment in xfont_create for more information. */
die("the first font in the cache must be loaded from a font string.");
}
fcpattern = FcPatternDuplicate(drw->fonts->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) {
usedfont = xfont_create(drw, NULL, match);
if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
; /* NOP */
curfont->next = usedfont;
} else {
xfont_free(usedfont);
usedfont = drw->fonts;
}
}
}
}
if (d)
XftDrawDestroy(d);
return x;
}

3
patch/scroll.h Normal file
View File

@ -0,0 +1,3 @@
enum { AlignL, AlignR };
int drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align);

110
patches.h Normal file
View File

@ -0,0 +1,110 @@
/* Patches */
/* This patch adds a border around the dmenu window. It is intended to be used with the center
* or xyw patches, to make the menu stand out from similarly coloured windows.
* http://tools.suckless.org/dmenu/patches/border/
*/
#define BORDER_PATCH 0
/* This patch centers dmenu in the middle of the screen.
* https://tools.suckless.org/dmenu/patches/center/
*/
#define CENTER_PATCH 0
/* This patch adds support for fuzzy-matching to dmenu, allowing users to type non-consecutive
* portions of the string to be matched.
* https://tools.suckless.org/dmenu/patches/fuzzymatch/
*/
#define FUZZYMATCH_PATCH 0
/* This patch causes dmenu to print out the current text each time a key is pressed.
* https://tools.suckless.org/dmenu/patches/incremental/
*/
#define INCREMENTAL_PATCH 0
/* This patch adds an option to provide preselected text.
* https://tools.suckless.org/dmenu/patches/initialtext/
*/
#define INITIALTEXT_PATCH 0
/* This patch adds a flag which will cause dmenu to select an item immediately if there
* is only one matching option left.
* https://tools.suckless.org/dmenu/patches/instant/
*/
#define INSTANT_PATCH 0
/* This patch adds a '-h' option which sets the minimum height of a dmenu line. This helps
* integrate dmenu with other UI elements that require a particular vertical size.
* http://tools.suckless.org/dmenu/patches/line-height/
*/
#define LINE_HEIGHT_PATCH 0
/* This patch adds basic mouse support for dmenu.
* https://tools.suckless.org/dmenu/patches/mouse-support/
*/
#define MOUSE_SUPPORT_PATCH 0
/* This patch provides dmenu the ability for history navigation similar to that of bash.
* https://tools.suckless.org/dmenu/patches/navhistory/
*/
#define NAVHISTORY_PATCH 0
/* This is a patch to have dmenu read stdin in a non blocking way, making it wait for input both
* from stdin and from X. This means that you can continue feeding dmenu while you type.
* This patch is meant to be used along with the incremental patch, so that you can use stdout
* to feed stdin.
* https://tools.suckless.org/dmenu/patches/non_blocking_stdin/
*/
#define NON_BLOCKING_STDIN_PATCH 0
/* With this patch dmenu will not directly display the keyboard input, but instead replace
* it with dots. All data from stdin will be ignored.
* https://tools.suckless.org/dmenu/patches/password/
*/
#define PASSWORD_PATCH 0
/* This patch allows the selected text to be piped back out with dmenu. This can be useful if you
* want to display the output of a command on the screen.
* Only text starting with the character '#' is piped out by default.
* https://tools.suckless.org/dmenu/patches/pipeout/
*/
#define PIPEOUT_PATCH 0
/* This patch changes the behaviour of matched items and the Tab key to allow tab completion.
* https://tools.suckless.org/dmenu/patches/prefix-completion/
*/
#define PREFIXCOMPLETION_PATCH 0
/* This patch adds a flag (-t) which makes Return key to ignore selection and print the input
* text to stdout. The flag basically swaps the functions of Return and Shift+Return hotkeys.
* https://tools.suckless.org/dmenu/patches/printinputtext/
*/
#define PRINTINPUTTEXT_PATCH 0
/* This patch adds a new flag to dmenu with which text input will be rejected if it would
* result in no matching item.
* https://tools.suckless.org/dmenu/patches/reject-no-match/
*/
#define REJECTNOMATCH_PATCH 0
/* This patch adds support for text scrolling and no longer appends '...' for long input as
* it can handle long text.
* https://tools.suckless.org/dmenu/patches/scroll/
*/
#define SCROLL_PATCH 0
/* This patch prevents dmenu from indenting items at the same level as the prompt length.
* https://tools.suckless.org/dmenu/patches/vertfull/
*/
#define VERTFULL_PATCH 0
/* Adds extended window manager hints such as _NET_WM_WINDOW_TYPE and _NET_WM_WINDOW_TYPE_DOCK.
* https://github.com/Baitinq/dmenu/blob/master/patches/dmenu-wm_type.diff
*/
#define WMTYPE_PATCH 0
/* This patch adds options for specifying dmenu window position and width.
* The center patch takes precedence over the XYW patch if enabled.
* https://tools.suckless.org/dmenu/patches/xyw/
*/
#define XYW_PATCH 0

90
stest.1 Normal file
View File

@ -0,0 +1,90 @@
.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)

109
stest.c Normal file
View File

@ -0,0 +1,109 @@
/* See LICENSE file for copyright and license details. */
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}

35
util.c Normal file
View File

@ -0,0 +1,35 @@
/* See LICENSE file for copyright and license details. */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
void *
ecalloc(size_t nmemb, size_t size)
{
void *p;
if (!(p = calloc(nmemb, size)))
die("calloc:");
return p;
}
void
die(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
exit(1);
}

8
util.h Normal file
View File

@ -0,0 +1,8 @@
/* 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 *fmt, ...);
void *ecalloc(size_t nmemb, size_t size);