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)