dmenu/dmenu.c
bakkeby dc169b1971 Adding separator patch
This patch is simpler than, and superior to, the TSV patch and as
such takes precedence if both are combined.

Also addressed some compatibility issues and compilation errors.
2022-09-05 14:08:27 +02:00

2189 lines
56 KiB
C

/* 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"
/* Patch incompatibility overrides */
#if MULTI_SELECTION_PATCH
#undef NON_BLOCKING_STDIN_PATCH
#undef PIPEOUT_PATCH
#undef PRINTINPUTTEXT_PATCH
#endif // MULTI_SELECTION_PATCH
#include "drw.h"
#include "util.h"
#if GRIDNAV_PATCH
#include <stdbool.h>
#endif // GRIDNAV_PATCH
/* 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])
#if PANGO_PATCH
#define TEXTW(X) (drw_font_getwidth(drw, (X), False) + lrpad)
#define TEXTWM(X) (drw_font_getwidth(drw, (X), True) + lrpad)
#else
#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
#endif // PANGO_PATCH
#if ALPHA_PATCH
#define OPAQUE 0xffU
#define OPACITY "_NET_WM_WINDOW_OPACITY"
#endif // ALPHA_PATCH
/* enums */
enum {
SchemeNorm,
SchemeSel,
SchemeOut,
#if BORDER_PATCH
SchemeBorder,
#endif // BORDER_PATCH
#if MORECOLOR_PATCH
SchemeMid,
#endif // MORECOLOR_PATCH
#if HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
SchemeNormHighlight,
SchemeSelHighlight,
#endif // HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
#if HIGHPRIORITY_PATCH
SchemeHp,
#endif // HIGHPRIORITY_PATCH
#if EMOJI_HIGHLIGHT_PATCH
SchemeHover,
SchemeGreen,
SchemeYellow,
SchemeBlue,
SchemePurple,
SchemeRed,
#endif // EMOJI_HIGHLIGHT_PATCH
SchemeLast,
}; /* color schemes */
struct item {
char *text;
#if SEPARATOR_PATCH
char *text_output;
#elif TSV_PATCH
char *stext;
#endif // SEPARATOR_PATCH | TSV_PATCH
struct item *left, *right;
#if NON_BLOCKING_STDIN_PATCH
struct item *next;
#endif // NON_BLOCKING_STDIN_PATCH
#if MULTI_SELECTION_PATCH
int id; /* for multiselect */
#else
int out;
#endif // MULTI_SELECTION_PATCH
#if HIGHPRIORITY_PATCH
int hp;
#endif // HIGHPRIORITY_PATCH
#if FUZZYMATCH_PATCH
double distance;
#endif // FUZZYMATCH_PATCH
#if PRINTINDEX_PATCH
int index;
#endif // PRINTINDEX_PATCH
};
static char text[BUFSIZ] = "";
#if PIPEOUT_PATCH
static char pipeout[8] = " | dmenu";
#endif // PIPEOUT_PATCH
static char *embed;
#if SEPARATOR_PATCH
static char separator;
static int separator_greedy;
static int separator_reverse;
#endif // SEPARATOR_PATCH
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 BARPADDING_PATCH
static int vp; /* vertical padding for bar */
static int sp; /* side padding for bar */
#endif // BARPADDING_PATCH
#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 PRINTINDEX_PATCH
static int print_index = 0;
#endif // PRINTINDEX_PATCH
#if MANAGED_PATCH
static int managed = 0;
#endif // MANAGED_PATCH
#if MULTI_SELECTION_PATCH
static int *selid = NULL;
static unsigned int selidsize = 0;
#endif // MULTI_SELECTION_PATCH
#if NO_SORT_PATCH
static unsigned int sortmatches = 1;
#endif // NO_SORT_PATCH
#if PRINTINPUTTEXT_PATCH
static int use_text_input = 0;
#endif // PRINTINPUTTEXT_PATCH
#if PRESELECT_PATCH
static unsigned int preselected = 0;
#endif // PRESELECT_PATCH
#if EMOJI_HIGHLIGHT_PATCH
static int commented = 0;
static int animated = 0;
#endif // EMOJI_HIGHLIGHT_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;
#if ALPHA_PATCH
static int useargb = 0;
static Visual *visual;
static int depth;
static Colormap cmap;
#endif // ALPHA_PATCH
static Drw *drw;
static Clr *scheme[SchemeLast];
#include "patch/include.h"
#include "config.h"
#if CASEINSENSITIVE_PATCH
static char * cistrstr(const char *s, const char *sub);
static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp;
static char *(*fstrstr)(const char *, const char *) = cistrstr;
#else
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr;
#endif // CASEINSENSITIVE_PATCH
static unsigned int
textw_clamp(const char *str, unsigned int n)
{
unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
return MIN(w, n);
}
static void 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);
#if ALPHA_PATCH
static void xinitvisual(void);
#endif // ALPHA_PATCH
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, rpad = 0;
if (lines > 0) {
#if GRID_PATCH
if (columns)
n = lines * columns * bh;
else
n = lines * bh;
#else
n = lines * bh;
#endif // GRID_PATCH
} else {
#if NUMBERS_PATCH
rpad = TEXTW(numbers);
#endif // NUMBERS_PATCH
#if SYMBOLS_PATCH
n = mw - (promptw + inputw + TEXTW(symbol_1) + TEXTW(symbol_2) + rpad);
#else
n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">") + rpad);
#endif // SYMBOLS_PATCH
}
/* 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 : textw_clamp(next->text, n)) > n)
break;
for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
if ((i += (lines > 0) ? bh : textw_clamp(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]);
for (i = 0; items && items[i].text; ++i)
free(items[i].text);
free(items);
#if HIGHPRIORITY_PATCH
for (i = 0; i < hplength; ++i)
free(hpitems[i]);
free(hpitems);
#endif // HIGHPRIORITY_PATCH
drw_free(drw);
XSync(dpy, False);
XCloseDisplay(dpy);
#if MULTI_SELECTION_PATCH
free(selid);
#endif // MULTI_SELECTION_PATCH
}
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)
{
int r;
#if TSV_PATCH && !SEPARATOR_PATCH
char *text = item->stext;
#else
char *text = item->text;
#endif // TSV_PATCH
#if EMOJI_HIGHLIGHT_PATCH
int iscomment = 0;
if (text[0] == '>') {
if (text[1] == '>') {
iscomment = 3;
switch (text[2]) {
case 'r':
drw_setscheme(drw, scheme[SchemeRed]);
break;
case 'g':
drw_setscheme(drw, scheme[SchemeGreen]);
break;
case 'y':
drw_setscheme(drw, scheme[SchemeYellow]);
break;
case 'b':
drw_setscheme(drw, scheme[SchemeBlue]);
break;
case 'p':
drw_setscheme(drw, scheme[SchemePurple]);
break;
#if HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
case 'h':
drw_setscheme(drw, scheme[SchemeNormHighlight]);
break;
#endif // HIGHLIGHT_PATCH | FUZZYHIGHLIGHT_PATCH
case 's':
drw_setscheme(drw, scheme[SchemeSel]);
break;
default:
iscomment = 1;
drw_setscheme(drw, scheme[SchemeNorm]);
break;
}
} else {
drw_setscheme(drw, scheme[SchemeNorm]);
iscomment = 1;
}
} else if (text[0] == ':') {
iscomment = 2;
if (item == sel) {
switch (text[1]) {
case 'r':
drw_setscheme(drw, scheme[SchemeRed]);
break;
case 'g':
drw_setscheme(drw, scheme[SchemeGreen]);
break;
case 'y':
drw_setscheme(drw, scheme[SchemeYellow]);
break;
case 'b':
drw_setscheme(drw, scheme[SchemeBlue]);
break;
case 'p':
drw_setscheme(drw, scheme[SchemePurple]);
break;
#if HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
case 'h':
drw_setscheme(drw, scheme[SchemeNormHighlight]);
break;
#endif // HIGHLIGHT_PATCH | FUZZYHIGHLIGHT_PATCH
case 's':
drw_setscheme(drw, scheme[SchemeSel]);
break;
default:
drw_setscheme(drw, scheme[SchemeSel]);
iscomment = 0;
break;
}
} else {
drw_setscheme(drw, scheme[SchemeNorm]);
}
}
#endif // EMOJI_HIGHLIGHT_PATCH
#if EMOJI_HIGHLIGHT_PATCH
int temppadding = 0;
if (iscomment == 2) {
if (text[2] == ' ') {
#if PANGO_PATCH
temppadding = drw->font->h * 3;
#else
temppadding = drw->fonts->h * 3;
#endif // PANGO_PATCH
animated = 1;
char dest[1000];
strcpy(dest, text);
dest[6] = '\0';
drw_text(drw, x, y
, temppadding
#if LINE_HEIGHT_PATCH
, MAX(lineheight, bh)
#else
, bh
#endif // LINE_HEIGHT_PATCH
, temppadding / 2.6
, dest + 3
, 0
#if PANGO_PATCH
, True
#endif // PANGO_PATCH
);
iscomment = 6;
drw_setscheme(drw, sel == item ? scheme[SchemeHover] : scheme[SchemeNorm]);
}
}
char *output;
if (commented) {
static char onestr[2];
onestr[0] = text[0];
onestr[1] = '\0';
output = onestr;
} else {
output = text;
}
#endif // EMOJI_HIGHLIGHT_PATCH
if (item == sel)
drw_setscheme(drw, scheme[SchemeSel]);
#if HIGHPRIORITY_PATCH
else if (item->hp)
drw_setscheme(drw, scheme[SchemeHp]);
#endif // HIGHPRIORITY_PATCH
#if MORECOLOR_PATCH
else if (item->left == sel || item->right == sel)
drw_setscheme(drw, scheme[SchemeMid]);
#endif // MORECOLOR_PATCH
#if MULTI_SELECTION_PATCH
else if (issel(item->id))
#else
else if (item->out)
#endif // MULTI_SELECTION_PATCH
drw_setscheme(drw, scheme[SchemeOut]);
else
drw_setscheme(drw, scheme[SchemeNorm]);
r = drw_text(drw
#if EMOJI_HIGHLIGHT_PATCH
, x + ((iscomment == 6) ? temppadding : 0)
#else
, x
#endif // EMOJI_HIGHLIGHT_PATCH
, y
, w
, bh
#if EMOJI_HIGHLIGHT_PATCH
, commented ? (bh - TEXTW(output) - lrpad) / 2 : lrpad / 2
#else
, lrpad / 2
#endif // EMOJI_HIGHLIGHT_PATCH
#if EMOJI_HIGHLIGHT_PATCH
, output + iscomment
#else
, text
#endif // EMOJI_HIGHLIGHT_PATCH
, 0
#if PANGO_PATCH
, True
#endif // PANGO_PATCH
);
#if HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
#if EMOJI_HIGHLIGHT_PATCH
drawhighlights(item, output + iscomment, x + ((iscomment == 6) ? temppadding : 0), y, w);
#else
drawhighlights(item, x, y, w);
#endif // EMOJI_HIGHLIGHT_PATCH
#endif // HIGHLIGHT_PATCH | FUZZYHIGHLIGHT_PATCH
return r;
}
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, rpad = 0, itw = 0, stw = 0;
#if LINE_HEIGHT_PATCH && PANGO_PATCH
int fh = drw->font->h;
#elif 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) {
#if !PLAIN_PROMPT_PATCH
drw_setscheme(drw, scheme[SchemeSel]);
#endif // PLAIN_PROMPT_PATCH
x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0
#if PANGO_PATCH
, True
#endif // PANGO_PATCH
);
}
/* draw input field */
w = (lines > 0 || !matches) ? mw - x : inputw;
#if SCROLL_PATCH
w -= lrpad / 2;
x += lrpad / 2;
rcurlen = TEXTW(text + cursor) - lrpad;
curlen = TEXTW(text) - lrpad - 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
#if PANGO_PATCH
, False
#endif // PANGO_PATCH
);
drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0
#if PANGO_PATCH
, False
#endif // PANGO_PATCH
);
free(censort);
} else {
drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0
#if PANGO_PATCH
, False
#endif // PANGO_PATCH
);
}
#else
drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0
#if PANGO_PATCH
, False
#endif // PANGO_PATCH
);
#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 NUMBERS_PATCH
recalculatenumbers();
rpad = TEXTW(numbers);
#if BARPADDING_PATCH
rpad += 2 * sp;
#endif // BARPADDING_PATCH
#if BORDER_PATCH
rpad += border_width;
#endif // BORDER_PATCH
#endif // NUMBERS_PATCH
if (lines > 0) {
#if GRID_PATCH
/* draw grid */
int i = 0;
for (item = curr; item != next; item = item->right, i++)
if (columns)
#if VERTFULL_PATCH
drawitem(
item,
0 + ((i / lines) * (mw / columns)),
y + (((i % lines) + 1) * bh),
mw / columns
);
#else
drawitem(
item,
x + ((i / lines) * ((mw - x) / columns)),
y + (((i % lines) + 1) * bh),
(mw - x) / columns
);
#endif // VERTFULL_PATCH
else
#if VERTFULL_PATCH
drawitem(item, 0, y += bh, mw);
#else
drawitem(item, x, y += bh, mw - x);
#endif // VERTFULL_PATCH
#else
/* 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
#endif // GRID_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
#if PANGO_PATCH
, True
#endif // PANGO_PATCH
);
}
x += w;
for (item = curr; item != next; item = item->right) {
#if SYMBOLS_PATCH
stw = TEXTW(symbol_2);
#else
stw = TEXTW(">");
#endif // SYMBOLS_PATCH
#if TSV_PATCH && !SEPARATOR_PATCH
itw = textw_clamp(item->stext, mw - x - stw - rpad);
#else
itw = textw_clamp(item->text, mw - x - stw - rpad);
#endif // TSV_PATCH
x = drawitem(item, x, 0, itw);
}
if (next) {
#if SYMBOLS_PATCH
w = TEXTW(symbol_2);
#else
w = TEXTW(">");
#endif // SYMBOLS_PATCH
drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, mw - w - rpad, 0, w, bh, lrpad / 2
#if SYMBOLS_PATCH
, symbol_2
#else
, ">"
#endif // SYMBOLS_PATCH
, 0
#if PANGO_PATCH
, True
#endif // PANGO_PATCH
);
}
}
#if NUMBERS_PATCH
drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, mw - rpad, 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0);
#endif // NUMBERS_PATCH
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;
#if !MANAGED_PATCH
XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
#endif // MANAGED_PATCH
nanosleep(&ts, NULL);
}
die("cannot grab focus");
}
static void
grabkeyboard(void)
{
struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
int i;
#if MANAGED_PATCH
if (embed || managed)
#else
if (embed)
#endif // MANAGED_PATCH
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 DYNAMIC_OPTIONS_PATCH
if (dynamic && *dynamic)
refreshoptions();
#endif // DYNAMIC_OPTIONS_PATCH
#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 HIGHPRIORITY_PATCH
struct item *lhpprefix, *hpprefixend;
#endif // HIGHPRIORITY_PATCH
#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 %zu 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 HIGHPRIORITY_PATCH
lhpprefix = hpprefixend = NULL;
#endif // HIGHPRIORITY_PATCH
#if NON_BLOCKING_STDIN_PATCH && DYNAMIC_OPTIONS_PATCH
for (item = items; item && (!(dynamic && *dynamic) || item->text); item = (dynamic && *dynamic) ? item + 1 : item->next)
#elif 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 DYNAMIC_OPTIONS_PATCH
if (i != tokc && !(dynamic && *dynamic)) /* not all tokens match */
continue;
#else
if (i != tokc) /* not all tokens match */
continue;
#endif // DYNAMIC_OPTIONS_PATCH
#if HIGHPRIORITY_PATCH
/* exact matches go first, then prefixes with high priority, then prefixes, then substrings */
#else
/* exact matches go first, then prefixes, then substrings */
#endif // HIGHPRIORITY_PATCH
#if NO_SORT_PATCH
if (!sortmatches)
appenditem(item, &matches, &matchend);
else
#endif // NO_SORT_PATCH
if (!tokc || !fstrncmp(text, item->text, textsize))
appenditem(item, &matches, &matchend);
#if HIGHPRIORITY_PATCH
else if (item->hp && !fstrncmp(tokv[0], item->text, len))
appenditem(item, &lhpprefix, &hpprefixend);
#endif // HIGHPRIORITY_PATCH
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 HIGHPRIORITY_PATCH
if (lhpprefix) {
if (matches) {
matchend->right = lhpprefix;
lhpprefix->left = matchend;
} else
matches = lhpprefix;
matchend = hpprefixend;
}
#endif // HIGHPRIORITY_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;
#if GRID_PATCH && GRIDNAV_PATCH
int i;
struct item *tmpsel;
bool offscreen = false;
#endif // GRIDNAV_PATCH
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) {
#if FZFEXPECT_PATCH
case XK_a: expect("ctrl-a", ev); ksym = XK_Home; break;
case XK_b: expect("ctrl-b", ev); ksym = XK_Left; break;
case XK_c: expect("ctrl-c", ev); ksym = XK_Escape; break;
case XK_d: expect("ctrl-d", ev); ksym = XK_Delete; break;
case XK_e: expect("ctrl-e", ev); ksym = XK_End; break;
case XK_f: expect("ctrl-f", ev); ksym = XK_Right; break;
case XK_g: expect("ctrl-g", ev); ksym = XK_Escape; break;
case XK_h: expect("ctrl-h", ev); ksym = XK_BackSpace; break;
case XK_i: expect("ctrl-i", ev); ksym = XK_Tab; break;
case XK_j: expect("ctrl-j", ev); ksym = XK_Down; break;
case XK_J:/* fallthrough */
case XK_l: expect("ctrl-l", ev); break;
case XK_m: expect("ctrl-m", ev); /* fallthrough */
case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
case XK_n: expect("ctrl-n", ev); ksym = XK_Down; break;
case XK_p: expect("ctrl-p", ev); ksym = XK_Up; break;
case XK_o: expect("ctrl-o", ev); break;
case XK_q: expect("ctrl-q", ev); break;
case XK_r: expect("ctrl-r", ev); break;
case XK_s: expect("ctrl-s", ev); break;
case XK_t: expect("ctrl-t", ev); break;
case XK_k: expect("ctrl-k", ev); ksym = XK_Up; break;
#else
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;
#endif // FZFEXPECT_PATCH
#if FZFEXPECT_PATCH
case XK_u: expect("ctrl-u", ev); /* delete left */
#else
case XK_u: /* delete left */
#endif // FZFEXPECT_PATCH
insert(NULL, 0 - cursor);
break;
#if FZFEXPECT_PATCH
case XK_w: expect("ctrl-w", ev); /* delete word */
#else
case XK_w: /* delete word */
#endif // FZFEXPECT_PATCH
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;
#if FZFEXPECT_PATCH || CTRL_V_TO_PASTE_PATCH
case XK_v:
#if FZFEXPECT_PATCH
expect("ctrl-v", ev);
#endif // FZFEXPECT_PATCH
case XK_V:
XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
utf8, utf8, win, CurrentTime);
return;
#endif // FZFEXPECT_PATCH | CTRL_V_TO_PASTE_PATCH
#if FZFEXPECT_PATCH
case XK_y: expect("ctrl-y", ev); /* paste selection */
#else
case XK_y: /* paste selection */
#endif // FZFEXPECT_PATCH
case XK_Y:
XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
utf8, utf8, win, CurrentTime);
return;
#if FZFEXPECT_PATCH
case XK_x: expect("ctrl-x", ev); break;
case XK_z: expect("ctrl-z", ev); break;
#endif // FZFEXPECT_PATCH
case XK_Left:
case XK_KP_Left:
movewordedge(-1);
goto draw;
case XK_Right:
case XK_KP_Right:
movewordedge(+1);
goto draw;
case XK_Return:
case XK_KP_Enter:
#if RESTRICT_RETURN_PATCH
if (restrict_return)
break;
#endif // RESTRICT_RETURN_PATCH
#if MULTI_SELECTION_PATCH
selsel();
#endif // MULTI_SELECTION_PATCH
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((unsigned char)*buf))
insert(buf, len);
break;
case XK_Delete:
case XK_KP_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:
case XK_KP_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:
case XK_KP_Home:
if (sel == matches) {
cursor = 0;
break;
}
sel = curr = matches;
calcoffsets();
break;
case XK_Left:
case XK_KP_Left:
#if GRID_PATCH && GRIDNAV_PATCH
if (columns > 1) {
if (!sel)
return;
tmpsel = sel;
for (i = 0; i < lines; i++) {
if (!tmpsel->left || tmpsel->left->right != tmpsel)
return;
if (tmpsel == curr)
offscreen = true;
tmpsel = tmpsel->left;
}
sel = tmpsel;
if (offscreen) {
curr = prev;
calcoffsets();
}
break;
}
#endif // GRIDNAV_PATCH
if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
cursor = nextrune(-1);
break;
}
if (lines > 0)
return;
/* fallthrough */
case XK_Up:
case XK_KP_Up:
if (sel && sel->left && (sel = sel->left)->right == curr) {
curr = prev;
calcoffsets();
}
break;
case XK_Next:
case XK_KP_Next:
if (!next)
return;
sel = curr = next;
calcoffsets();
break;
case XK_Prior:
case XK_KP_Prior:
if (!prev)
return;
sel = curr = prev;
calcoffsets();
break;
case XK_Return:
case XK_KP_Enter:
#if RESTRICT_RETURN_PATCH
if (restrict_return && (!sel || ev->state & (ShiftMask | ControlMask)))
break;
#endif // RESTRICT_RETURN_PATCH
#if !MULTI_SELECTION_PATCH
#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);
}
#if PRINTINDEX_PATCH
if (print_index)
printf("%d\n", sel->index);
else
#endif // PRINTINDEX_PATCH
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);
#if PRINTINDEX_PATCH
else if (print_index)
printf("%d\n", (sel && !(ev->state & ShiftMask)) ? sel->index : -1);
#endif // PRINTINDEX_PATCH
else
#if SEPARATOR_PATCH
puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : text);
#else
puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
#endif // SEPARATOR_PATCH
#elif PRINTINDEX_PATCH
if (print_index)
printf("%d\n", (sel && !(ev->state & ShiftMask)) ? sel->index : -1);
else
#if SEPARATOR_PATCH
puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : text);
#else
puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
#endif // SEPARATOR_PATCH
#elif SEPARATOR_PATCH
puts((sel && !(ev->state & ShiftMask)) ? sel->text_output : text);
#else
puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
#endif // PIPEOUT_PATCH | PRINTINPUTTEXT_PATCH | PRINTINDEX_PATCH
#endif // MULTI_SELECTION_PATCH
if (!(ev->state & ControlMask)) {
#if NAVHISTORY_PATCH
savehistory((sel && !(ev->state & ShiftMask))
? sel->text : text);
#endif // NAVHISTORY_PATCH
#if MULTI_SELECTION_PATCH
printsel(ev->state);
#endif // MULTI_SELECTION_PATCH
cleanup();
exit(0);
}
#if !MULTI_SELECTION_PATCH
if (sel)
sel->out = 1;
#endif // MULTI_SELECTION_PATCH
break;
case XK_Right:
case XK_KP_Right:
#if GRID_PATCH && GRIDNAV_PATCH
if (columns > 1) {
if (!sel)
return;
tmpsel = sel;
for (i = 0; i < lines; i++) {
if (!tmpsel->right || tmpsel->right->left != tmpsel)
return;
tmpsel = tmpsel->right;
if (tmpsel == next)
offscreen = true;
}
sel = tmpsel;
if (offscreen) {
curr = next;
calcoffsets();
}
break;
}
#endif // GRIDNAV_PATCH
if (text[cursor] != '\0') {
cursor = nextrune(+1);
break;
}
if (lines > 0)
return;
/* fallthrough */
case XK_Down:
case XK_KP_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 */
#if FUZZYMATCH_PATCH
/* only do tab completion if all matches start with prefix */
for (item = matches; item && item->text; item = item->right)
if (item->text[0] != text[0])
goto draw;
#endif // FUZZYMATCH_PATCH
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;
cursor = strnlen(sel->text, sizeof text - 1);
memcpy(text, sel->text, cursor);
text[cursor] = '\0';
match();
#endif // PREFIXCOMPLETION_PATCH
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 ALPHA_PATCH
static void
xinitvisual()
{
XVisualInfo *infos;
XRenderPictFormat *fmt;
int nitems;
int i;
XVisualInfo tpl = {
.screen = screen,
.depth = 32,
.class = TrueColor
};
long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
infos = XGetVisualInfo(dpy, masks, &tpl, &nitems);
visual = NULL;
for(i = 0; i < nitems; i ++) {
fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
visual = infos[i].visual;
depth = infos[i].depth;
cmap = XCreateColormap(dpy, root, visual, AllocNone);
useargb = 1;
break;
}
}
XFree(infos);
if (!visual || !opacity) {
visual = DefaultVisual(dpy, screen);
depth = DefaultDepth(dpy, screen);
cmap = DefaultColormap(dpy, screen);
}
}
#endif // ALPHA_PATCH
#if !NON_BLOCKING_STDIN_PATCH
static void
readstdin(void)
{
char *line = NULL;
#if SEPARATOR_PATCH
char *p;
#elif TSV_PATCH
char *buf, *p;
#endif // SEPARATOR_PATCH | TSV_PATCH
size_t size = 0;
size_t i, junk;
ssize_t len;
#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; (len = getline(&line, &junk, stdin)) != -1; i++, line = NULL) {
if (i + 1 >= size / sizeof *items)
if (!(items = realloc(items, (size += BUFSIZ))))
die("cannot realloc %zu bytes:", size);
if (line[len - 1] == '\n')
line[len - 1] = '\0';
items[i].text = line;
#if SEPARATOR_PATCH
if (separator && (p = separator_greedy ?
strrchr(items[i].text, separator) : strchr(items[i].text, separator))) {
*p = '\0';
items[i].text_output = ++p;
} else {
items[i].text_output = items[i].text;
}
if (separator_reverse) {
p = items[i].text;
items[i].text = items[i].text_output;
items[i].text_output = p;
}
#elif TSV_PATCH
if (!(buf = strdup(line)))
die("cannot strdup %u bytes:", strlen(line) + 1);
if ((p = strchr(buf, '\t')))
*p = '\0';
items[i].stext = buf;
#endif // SEPARATOR_PATCH | TSV_PATCH
#if MULTI_SELECTION_PATCH
items[i].id = i; /* for multiselect */
#if PRINTINDEX_PATCH
items[i].index = i;
#endif // PRINTINDEX_PATCH
#elif PRINTINDEX_PATCH
items[i].index = i;
#else
items[i].out = 0;
#endif // MULTI_SELECTION_PATCH | PRINTINDEX_PATCH
#if HIGHPRIORITY_PATCH
items[i].hp = arrayhas(hpitems, hplength, items[i].text);
#endif // HIGHPRIORITY_PATCH
}
if (items)
items[i].text = NULL;
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;
#if PRESELECT_PATCH
int i;
#endif // PRESELECT_PATCH
while (!XNextEvent(dpy, &ev)) {
#if PRESELECT_PATCH
if (preselected) {
for (i = 0; i < preselected; i++) {
if (sel && sel->right && (sel = sel->right) == next) {
curr = next;
calcoffsets();
}
}
drawmenu();
preselected = 0;
}
#endif // PRESELECT_PATCH
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;
#if RELATIVE_INPUT_WIDTH_PATCH
unsigned int tmp, minstrlen = 0, curstrlen = 0;
int numwidthchecks = 100;
struct item *item;
#endif // RELATIVE_INPUT_WIDTH_PATCH
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 */
#if XRESOURCES_PATCH
for (j = 0; j < SchemeLast; j++)
#if ALPHA_PATCH
scheme[j] = drw_scm_create(drw, (const char**)colors[j], alphas[j], 2);
#else
scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2);
#endif // ALPHA_PATCH
#else
for (j = 0; j < SchemeLast; j++)
#if ALPHA_PATCH
scheme[j] = drw_scm_create(drw, colors[j], alphas[j], 2);
#else
scheme[j] = drw_scm_create(drw, colors[j], 2);
#endif // ALPHA_PATCH
#endif // XRESOURCES_PATCH
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 */
#if PANGO_PATCH
bh = drw->font->h + 2;
#else
bh = drw->fonts->h + 2;
#endif // PANGO_PATCH
#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 && PANGO_PATCH
promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0;
#elif CENTER_PATCH
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]) != 0)
break;
#if CENTER_PATCH
if (center) {
mw = MIN(MAX(max_textw() + promptw, min_width), 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, min_width), 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 PANGO_PATCH
promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0;
#else
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#endif // PANGO_PATCH
#endif // CENTER_PATCH
#if RELATIVE_INPUT_WIDTH_PATCH
for (item = items; !lines && item && item->text; ++item) {
curstrlen = strlen(item->text);
if (numwidthchecks || minstrlen < curstrlen) {
numwidthchecks = MAX(numwidthchecks - 1, 0);
minstrlen = MAX(minstrlen, curstrlen);
if ((tmp = textw_clamp(item->text, mw/3)) > inputw) {
inputw = tmp;
if (tmp == mw/3)
break;
}
}
}
#else
inputw = mw / 3; /* input width: ~33.33% of monitor width */
#endif // RELATIVE_INPUT_WIDTH_PATCH
match();
/* create menu window */
#if MANAGED_PATCH
swa.override_redirect = managed ? False : True;
#else
swa.override_redirect = True;
#endif // MANAGED_PATCH
#if ALPHA_PATCH
swa.background_pixel = 0;
swa.colormap = cmap;
#else
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
#endif // ALPHA_PATCH
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask
#if MOUSE_SUPPORT_PATCH
| ButtonPressMask
#endif // MOUSE_SUPPORT_PATCH
;
win = XCreateWindow(
dpy, parentwin,
#if BARPADDING_PATCH && BORDER_PATCH
x + sp, y + vp - (topbar ? 0 : border_width * 2), mw - 2 * sp - border_width * 2, mh, border_width,
#elif BARPADDING_PATCH
x + sp, y + vp, mw - 2 * sp, mh, 0,
#elif BORDER_PATCH
x, y - (topbar ? 0 : border_width * 2), mw - border_width * 2, mh, border_width,
#else
x, y, mw, mh, 0,
#endif // BORDER_PATCH | BARPADDING_PATCH
#if ALPHA_PATCH
depth, InputOutput, visual,
CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &swa
#else
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa
#endif // ALPHA_PATCH
);
#if BORDER_PATCH
if (border_width)
XSetWindowBorder(dpy, win, scheme[SchemeBorder][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);
#if MANAGED_PATCH
if (managed) {
XTextProperty prop;
char *windowtitle = prompt != NULL ? prompt : "dmenu";
Xutf8TextListToTextProperty(dpy, &windowtitle, 1, XUTF8StringStyle, &prop);
XSetWMName(dpy, win, &prop);
XSetTextProperty(dpy, win, &prop, XInternAtom(dpy, "_NET_WM_NAME", False));
XFree(prop.value);
}
#endif // MANAGED_PATCH
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 [-bv"
#if CENTER_PATCH
"c"
#endif
#if !NON_BLOCKING_STDIN_PATCH
"f"
#endif // NON_BLOCKING_STDIN_PATCH
#if INCREMENTAL_PATCH
"r"
#endif // INCREMENTAL_PATCH
#if CASEINSENSITIVE_PATCH
"s"
#else
"i"
#endif // CASEINSENSITIVE_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 NO_SORT_PATCH
"S"
#endif // NO_SORT_PATCH
#if REJECTNOMATCH_PATCH
"R" // (changed from r to R due to conflict with INCREMENTAL_PATCH)
#endif // REJECTNOMATCH_PATCH
#if RESTRICT_RETURN_PATCH
"1"
#endif // RESTRICT_RETURN_PATCH
"] "
#if MANAGED_PATCH
"[-wm] "
#endif // MANAGED_PATCH
#if GRID_PATCH
"[-g columns] "
#endif // GRID_PATCH
"[-l lines] [-p prompt] [-fn font] [-m monitor]"
"\n [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"
#if DYNAMIC_OPTIONS_PATCH || FZFEXPECT_PATCH || ALPHA_PATCH || BORDER_PATCH || HIGHPRIORITY_PATCH
"\n "
#endif
#if DYNAMIC_OPTIONS_PATCH
" [-dy command]"
#endif // DYNAMIC_OPTIONS_PATCH
#if FZFEXPECT_PATCH
" [-ex expectkey]"
#endif // FZFEXPECT_PATCH
#if ALPHA_PATCH
" [-o opacity]"
#endif // ALPHA_PATCH
#if BORDER_PATCH
" [-bw width]"
#endif // BORDER_PATCH
#if HIGHPRIORITY_PATCH
" [-hb color] [-hf color] [-hp items]"
#endif // HIGHPRIORITY_PATCH
#if INITIALTEXT_PATCH || LINE_HEIGHT_PATCH || PRESELECT_PATCH || NAVHISTORY_PATCH || XYW_PATCH
"\n "
#endif
#if INITIALTEXT_PATCH
" [-it text]"
#endif // INITIALTEXT_PATCH
#if LINE_HEIGHT_PATCH
" [-h height]"
#endif // LINE_HEIGHT_PATCH
#if PRESELECT_PATCH
" [-ps index]"
#endif // PRESELECT_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
#if HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
"\n [-nhb color] [-nhf color] [-shb color] [-shf color]" // highlight colors
#endif // HIGHLIGHT_PATCH | FUZZYHIGHLIGHT_PATCH
#if SEPARATOR_PATCH
"\n [-d separator] [-D separator]"
#endif // SEPARATOR_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
#if XRESOURCES_PATCH
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);
#if ALPHA_PATCH
xinitvisual();
drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap);
#else
drw = drw_create(dpy, screen, root, wa.width, wa.height);
#endif // ALPHA_PATCH
readxresources();
#endif // XRESOURCES_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 // CENTER_PATCH
#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
#if CASEINSENSITIVE_PATCH
} else if (!strcmp(argv[i], "-s")) { /* case-sensitive item matching */
fstrncmp = strncmp;
fstrstr = strstr;
#else
} else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
fstrncmp = strncasecmp;
fstrstr = cistrstr;
#endif // CASEINSENSITIVE_PATCH
#if MANAGED_PATCH
} else if (!strcmp(argv[i], "-wm")) { /* display as managed wm window */
managed = 1;
#endif // MANAGED_PATCH
#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 FZFEXPECT_PATCH
} else if (!strcmp(argv[i], "-ex")) { /* expect key */
expected = argv[++i];
#endif // FZFEXPECT_PATCH
#if REJECTNOMATCH_PATCH
} else if (!strcmp(argv[i], "-R")) { /* reject input which results in no match */
reject_no_match = 1;
#endif // REJECTNOMATCH_PATCH
#if NO_SORT_PATCH
} else if (!strcmp(argv[i], "-S")) { /* do not sort matches */
sortmatches = 0;
#endif // NO_SORT_PATCH
#if PRINTINDEX_PATCH
} else if (!strcmp(argv[i], "-ix")) { /* adds ability to return index in list */
print_index = 1;
#endif // PRINTINDEX_PATCH
#if RESTRICT_RETURN_PATCH
} else if (!strcmp(argv[i], "-1")) {
restrict_return = 1;
#endif // RESTRICT_RETURN_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
#if GRID_PATCH
else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */
columns = atoi(argv[++i]);
if (columns && lines == 0)
lines = 1;
}
#endif // GRID_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]);
#if ALPHA_PATCH
else if (!strcmp(argv[i], "-o")) /* opacity, pass -o 0 to disable alpha */
opacity = atoi(argv[++i]);
#endif // ALPHA_PATCH
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 */
#if PANGO_PATCH
strcpy(font, argv[++i]);
#else
fonts[0] = argv[++i];
#endif // PANGO_PATCH
#if LINE_HEIGHT_PATCH
else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */
lineheight = atoi(argv[++i]);
lineheight = MAX(lineheight, min_lineheight); /* 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];
#if HIGHPRIORITY_PATCH
else if (!strcmp(argv[i], "-hb")) /* high priority background color */
colors[SchemeHp][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-hf")) /* low priority background color */
colors[SchemeHp][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-hp"))
hpitems = tokenize(argv[++i], ",", &hplength);
#endif // HIGHPRIORITY_PATCH
#if HIGHLIGHT_PATCH || FUZZYHIGHLIGHT_PATCH
else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */
colors[SchemeNormHighlight][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */
colors[SchemeNormHighlight][ColFg] = argv[++i];
else if (!strcmp(argv[i], "-shb")) /* selected hi background color */
colors[SchemeSelHighlight][ColBg] = argv[++i];
else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */
colors[SchemeSelHighlight][ColFg] = argv[++i];
#endif // HIGHLIGHT_PATCH | FUZZYHIGHLIGHT_PATCH
else if (!strcmp(argv[i], "-w")) /* embedding window id */
embed = argv[++i];
#if SEPARATOR_PATCH
else if (!strcmp(argv[i], "-d") || /* field separator */
(separator_greedy = !strcmp(argv[i], "-D"))) {
separator = argv[++i][0];
separator_reverse = argv[i][1] == '|';
}
#endif // SEPARATOR_PATCH
#if PRESELECT_PATCH
else if (!strcmp(argv[i], "-ps")) /* preselected item */
preselected = atoi(argv[++i]);
#endif // PRESELECT_PATCH
#if DYNAMIC_OPTIONS_PATCH
else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */
dynamic = argv[++i];
#endif // DYNAMIC_OPTIONS_PATCH
#if BORDER_PATCH
else if (!strcmp(argv[i], "-bw")) /* border width around dmenu */
border_width = atoi(argv[++i]);
#endif // BORDER_PATCH
#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 XRESOURCES_PATCH
#if PANGO_PATCH
if (!drw_font_create(drw, font))
die("no fonts could be loaded.");
#else
if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts)))
die("no fonts could be loaded.");
#endif // PANGO_PATCH
#else // !XRESOURCES_PATCH
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);
#if ALPHA_PATCH
xinitvisual();
drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap);
#else
drw = drw_create(dpy, screen, root, wa.width, wa.height);
#endif // ALPHA_PATCH
#if PANGO_PATCH
if (!drw_font_create(drw, font))
die("no fonts could be loaded.");
#else
if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
die("no fonts could be loaded.");
#endif // PANGO_PATCH
#endif // XRESOURCES_PATCH
#if PANGO_PATCH
lrpad = drw->font->h;
#else
lrpad = drw->fonts->h;
#endif // PANGO_PATCH
#if BARPADDING_PATCH
sp = sidepad;
vp = (topbar ? vertpad : - vertpad);
#endif // BARPADDING_PATCH
#if LINE_HEIGHT_PATCH
if (lineheight == -1)
#if PANGO_PATCH
lineheight = drw->font->h * 2.5;
#else
lineheight = drw->fonts->h * 2.5;
#endif // PANGO_PATCH
#endif // LINE_HEIGHT_PATCH
#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();
#if DYNAMIC_OPTIONS_PATCH
if (!(dynamic && *dynamic))
readstdin();
#else
readstdin();
#endif // DYNAMIC_OPTIONS_PATCH
} else {
#if DYNAMIC_OPTIONS_PATCH
if (!(dynamic && *dynamic))
readstdin();
#else
readstdin();
#endif // DYNAMIC_OPTIONS_PATCH
grabkeyboard();
}
#endif // NON_BLOCKING_STDIN_PATCH
setup();
run();
return 1; /* unreachable */
}