diff options
Diffstat (limited to 'dmenu')
35 files changed, 5101 insertions, 1390 deletions
diff --git a/dmenu/FUNDING.yml b/dmenu/FUNDING.yml deleted file mode 100644 index c7c9a22..0000000 --- a/dmenu/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -custom: ["https://lukesmith.xyz/donate.html"] -github: lukesmithxyz diff --git a/dmenu/LICENSE b/dmenu/LICENSE index 3afd28e..2a64b28 100644 --- a/dmenu/LICENSE +++ b/dmenu/LICENSE @@ -8,7 +8,7 @@ MIT/X Consortium License © 2009 Markus Schnalke <meillo@marmaro.de> © 2009 Evan Gates <evan.gates@gmail.com> © 2010-2012 Connor Lane Smith <cls@lubutu.com> -© 2014-2020 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org> © 2015-2019 Quentin Rameau <quinq@fifth.space> Permission is hereby granted, free of charge, to any person obtaining a diff --git a/dmenu/Makefile b/dmenu/Makefile index 244961a..f157bb1 100644 --- a/dmenu/Makefile +++ b/dmenu/Makefile @@ -6,13 +6,7 @@ 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)" +all: dmenu stest .c.o: $(CC) -c $(CFLAGS) $< @@ -29,12 +23,12 @@ stest: stest.o $(CC) -o $@ stest.o $(LDFLAGS) clean: - rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz *.rej *.orig + 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)\ + drw.h util.h dmenu_path dmenu_path_desktop dmenu_run dmenu_run_desktop stest.1 $(SRC)\ dmenu-$(VERSION) tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) gzip dmenu-$(VERSION).tar @@ -42,10 +36,12 @@ dist: clean install: all mkdir -p $(DESTDIR)$(PREFIX)/bin - cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_path_desktop dmenu_run dmenu_run_desktop stest $(DESTDIR)$(PREFIX)/bin chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path_desktop chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run_desktop chmod 755 $(DESTDIR)$(PREFIX)/bin/stest mkdir -p $(DESTDIR)$(MANPREFIX)/man1 sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 @@ -56,9 +52,11 @@ install: all uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path_desktop\ $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run_desktop\ $(DESTDIR)$(PREFIX)/bin/stest\ $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ $(DESTDIR)$(MANPREFIX)/man1/stest.1 -.PHONY: all options clean dist install uninstall +.PHONY: all clean dist install uninstall diff --git a/dmenu/README b/dmenu/README new file mode 100644 index 0000000..a8fcdfe --- /dev/null +++ b/dmenu/README @@ -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. diff --git a/dmenu/README.md b/dmenu/README.md deleted file mode 100644 index c8e612e..0000000 --- a/dmenu/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Luke's dmenu - -Extra stuff added to vanilla dmenu: - -- reads Xresources (ergo pywal compatible) -- alpha patch, which importantly allows this build to be embedded in transparent st -- can view color characters like emoji -- `-P` for password mode: hide user input -- `-r` to reject non-matching input -- dmenu options are mouse clickable - -## Installation - -After making any config changes that you want, but `make`, `sudo make install` it. diff --git a/dmenu/config.def.h b/dmenu/config.def.h new file mode 100644 index 0000000..5c44fa7 --- /dev/null +++ b/dmenu/config.def.h @@ -0,0 +1,106 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +#define PADDING 0 + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ +static int dmx = PADDING; /* put dmenu at this x offset */ +static int dmy = PADDING; /* put dmenu at this y offset (measured from the bottom if topbar is 0) */ +static const unsigned int alpha = 0xe0; /* Amount of opacity. 0xff is opaque */ +static unsigned int dmw = 0; /* make dmenu this wide */ +static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ +static char *prompt = NULL; /* -p option; prompt to the left of input field */ + +/* Size of the window border */ +static unsigned int border_width = 0; + +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; +static unsigned int maxhist = 64; +static int histnodup = 1; /* if 0, record repeated histories */ + +/* -h option; minimum height of a menu line */ +static unsigned int lineheight = 0; +static unsigned int min_lineheight = 8; + +/* + * -vi option; if nonzero, vi mode is always enabled and can be + * accessed with the global_esc keysym + mod mask + */ +static unsigned int vi_mode = 1; +static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ +static Key global_esc = { XK_Escape, 0}; /* escape key when vi mode is not enabled explicitly */ +static Key quit_keys[] = { + /* keysym modifier */ + { XK_q, 0 }, + { XK_Escape, 0 } +}; + +/* -fn option overrides fonts[0]; default X11 font or font set */ +static char font[] = "monospace:size=10"; +static const char *fonts[] = { + font, + "monospace:size=10", +}; + +static char normfgcolor[] = "#bbbbbb"; +static char normbgcolor[] = "#222222"; +static char selfgcolor[] = "#eeeeee"; +static char selbgcolor[] = "#005577"; +static char outfgcolor[] = "#000000"; +static char outbgcolor[] = "#00ffff"; +static char selhlfgcolor[] = "#ffc978"; +static char selhlbgcolor[] = "#005577"; +static char normhlfgcolor[] = "#ffc978"; +static char normhlbgcolor[] = "#222222"; +static char caretfgcolor[] = "#eeeeee"; +static char caretbgcolor[] = "#222222"; +static char cursorfgcolor[] = "#222222"; +static char cursorbgcolor[] = "#bbbbbb"; + +static char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { normfgcolor, normbgcolor }, + [SchemeSel] = { selfgcolor, selbgcolor }, + [SchemeOut] = { outfgcolor, outbgcolor }, + [SchemeSelHighlight] = { selhlfgcolor, selhlbgcolor }, + [SchemeNormHighlight] = { normhlfgcolor, normhlbgcolor }, + [SchemeCaret] = { caretfgcolor, caretbgcolor }, + [SchemeCursor] = { cursorfgcolor, cursorbgcolor}, +}; + +static const unsigned int alphas[SchemeLast][2] = { + [SchemeNorm] = { OPAQUE, alpha }, + [SchemeSel] = { OPAQUE, alpha }, + [SchemeOut] = { OPAQUE, alpha }, +}; + +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "normfgcolor", STRING, &normfgcolor }, + { "normbgcolor", STRING, &normbgcolor }, + { "selfgcolor", STRING, &selfgcolor }, + { "selbgcolor", STRING, &selbgcolor }, + { "outfgcolor", STRING, &outfgcolor }, + { "outbgcolor", STRING, &outbgcolor }, + { "selhlfgcolor", STRING, &selhlfgcolor }, + { "selhlbgcolor", STRING, &selhlbgcolor }, + { "normhlfgcolor", STRING, &normhlfgcolor }, + { "normhlbgcolor", STRING, &normhlbgcolor }, + { "caretfgcolor", STRING, &caretfgcolor }, + { "caretbgcolor", STRING, &caretbgcolor }, + { "cursorfgcolor", STRING, &cursorfgcolor }, + { "cursorbgcolor", STRING, &cursorbgcolor }, + { "prompt", STRING, &prompt }, +}; + diff --git a/dmenu/config.h b/dmenu/config.h deleted file mode 100644 index 9ed5636..0000000 --- a/dmenu/config.h +++ /dev/null @@ -1,53 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -/* Default settings; can be overriden by command line. */ - -#define PADDING 0 - -static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ -static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ -static int dmx = PADDING; /* put dmenu at this x offset */ -static int dmy = PADDING; /* put dmenu at this y offset (measured from the bottom if topbar is 0) */ -static unsigned int dmw = 0; /* make dmenu this wide */ -/* -fn option overrides fonts[0]; default X11 font or font set */ -static const char *fonts[] = { - "monospace:size=10", - "NotoColorEmoji:pixelsize=8:antialias=true:autohint=true"}; -static const unsigned int bgalpha = 0xe0; -static const unsigned int fgalpha = OPAQUE; -static const char *prompt = NULL; /* -p option; prompt to the left of input field */ -static const char *colors[SchemeLast][2] = { - /* fg bg */ - [SchemeNorm] = {"#bbbbbb", "#222222"}, - [SchemeSel] = {"#eeeeee", "#005577"}, - [SchemeOut] = {"#000000", "#00ffff"}, - [SchemeCursor] = {"#222222", "#bbbbbb"}, -}; -static const unsigned int alphas[SchemeLast][2] = { - /* fgalpha bgalphga */ - [SchemeNorm] = {fgalpha, bgalpha}, - [SchemeSel] = {fgalpha, bgalpha}, - [SchemeOut] = {fgalpha, bgalpha}, -}; - -/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ -static unsigned int lines = 0; - -/* - * Characters not considered part of a word while deleting words - * for example: " /?\"&[]" - */ -static const char worddelimiters[] = " "; - -/* - * -vi option; if nonzero, vi mode is always enabled and can be - * accessed with the global_esc keysym + mod mask - */ -static unsigned int vi_mode = 1; -static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ -static Key global_esc = { XK_Escape, 0}; /* escape key when vi mode is not enabled explicitly */ -static Key quit_keys[] = { - /* keysym modifier */ - { XK_q, 0 }, - { XK_Escape, 0 } -}; - diff --git a/dmenu/config.mk b/dmenu/config.mk index 1641c06..9ec2ccf 100644 --- a/dmenu/config.mk +++ b/dmenu/config.mk @@ -1,5 +1,5 @@ # dmenu version -VERSION = 5.0 +VERSION = 5.3 # paths PREFIX = /usr/local @@ -17,10 +17,11 @@ FREETYPELIBS = -lfontconfig -lXft FREETYPEINC = /usr/include/freetype2 # OpenBSD (uncomment) #FREETYPEINC = $(X11INC)/freetype2 +#MANPREFIX = ${PREFIX}/man # includes and libs -INCS = -I$(X11INC) -I$(FREETYPEINC) -LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender -lm +INCS = -I$(X11INC) -I$(FREETYPEINC) $(JANSSONINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender -lm -lspng # flags CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1 index 7202c6e..d52faba 100644 --- a/dmenu/dmenu.1 +++ b/dmenu/dmenu.1 @@ -3,9 +3,11 @@ dmenu \- dynamic menu .SH SYNOPSIS .B dmenu -.RB [ \-bfirvP ] +.RB [ \-bFfivP ] .RB [ \-l .IR lines ] +.RB [ \-h +.IR height ] .RB [ \-m .IR monitor ] .RB [ \-x @@ -26,8 +28,20 @@ dmenu \- dynamic menu .IR color ] .RB [ \-sf .IR color ] +.RB [ \-nhb +.IR color ] +.RB [ \-nhf +.IR color ] +.RB [ \-shb +.IR color ] +.RB [ \-shf +.IR color ] .RB [ \-w .IR windowid ] +.RB [ \-H +.IR histfile ] +.RB [ \-dy +.IR command ] .P .BR dmenu_run " ..." .SH DESCRIPTION @@ -46,6 +60,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. .B \-b dmenu appears at the bottom of the screen. .TP +.B \-F +disables fuzzy matching. +.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. @@ -56,15 +73,12 @@ dmenu matches menu items case insensitively. .B \-P dmenu will not directly display the keyboard input, but instead replace it with dots. All data from stdin will be ignored. .TP -.B \-r -dmenu will reject any input which would result in no matching option left. -.TP -.B \-S -dmenu does not sort menu items after matching. -.TP .BI \-l " lines" dmenu lists items vertically, with the given number of lines. .TP +.BI \-h " height" +dmenu uses a menu line of at least 'height' pixels tall, but no less than 8. +.TP .BI \-m " monitor" dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0. @@ -108,11 +122,29 @@ defines the selected background color. .BI \-sf " color" defines the selected foreground color. .TP +.BI \-nhb " color" +defines the normal highlight background color. +.TP +.BI \-nhf " color" +defines the normal highlight foreground color. +.TP +.BI \-shb " color" +defines the selected highlight background color. +.TP +.BI \-shf " color" +defines the selected highlight foreground color. +.TP .B \-v prints version information to stdout, then exits. .TP .BI \-w " windowid" embed into windowid. +.TP +.BI \-H " histfile" +save input in histfile and use it for history navigation. +.TP +.BI \-dy " command" +runs command whenever input changes to update menu items. .SH USAGE dmenu is completely controlled by the keyboard. Items are selected using the arrow keys, page up, page down, home, and end. diff --git a/dmenu/dmenu.c b/dmenu/dmenu.c index a2257b5..82b73ca 100644 --- a/dmenu/dmenu.c +++ b/dmenu/dmenu.c @@ -1,7 +1,6 @@ /* See LICENSE file for copyright and license details. */ #include <ctype.h> #include <locale.h> -#include <stdbool.h> #include <math.h> #include <stdio.h> #include <stdlib.h> @@ -10,47 +9,41 @@ #include <time.h> #include <unistd.h> -#include <X11/Xatom.h> #include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xproto.h> #include <X11/Xutil.h> +#include <X11/Xresource.h> #ifdef XINERAMA #include <X11/extensions/Xinerama.h> #endif +#include <X11/extensions/Xrender.h> #include <X11/Xft/Xft.h> -#include <X11/Xresource.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) +#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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) -/* define opaqueness */ -#define OPAQUE 0xFFU +#define OPAQUE 0xffu /* enums */ -enum { - SchemeNorm, - SchemeSel, - SchemeOut, - SchemeCursor, - SchemeLast -}; /* color schemes */ +enum { SchemeNorm, SchemeSel, SchemeCaret, SchemeCursor, SchemeNormHighlight, SchemeSelHighlight, + SchemeOut, SchemeLast }; /* color schemes */ struct item { - char *text; - struct item *left, *right; - int out; - double distance; + char *text; + struct item *left, *right; + int out; + double distance; }; typedef struct { - KeySym ksym; - unsigned int state; + KeySym ksym; + unsigned int state; } Key; static char text[BUFSIZ] = ""; @@ -58,14 +51,16 @@ static char *embed; static int bh, mw, mh; static int inputw = 0, promptw, passwd = 0; static int lrpad; /* sum of left and right padding */ -static int reject_no_match = 0; +static int tbpad; /* sum of top and bottom padding for images */ static size_t cursor; static struct item *items = NULL; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel; static int mon = -1, screen; static unsigned int using_vi_mode = 0; -static bool sortmatches = true; +static unsigned int max_lines = 0; +static char *image_prefix = "PNG_IMAGE:"; +static int image_size = -1; /* in pixels */ static Atom clip, utf8; static Display *dpy; @@ -73,1259 +68,1617 @@ static Window root, parentwin, win; static XIC xic; static Drw *drw; -static int usergb = 0; +static Clr *scheme[SchemeLast]; + +static int useargb = 0; static Visual *visual; static int depth; static Colormap cmap; -static Clr *scheme[SchemeLast]; + +static char *histfile; +static char **history; +static size_t histsz, histpos; + +/* Xresources preferences */ +enum resource_type { + STRING = 0, + INTEGER = 1, + FLOAT = 2 +}; +typedef struct { + char *name; + enum resource_type type; + void *dst; +} ResourcePref; + +static void load_xresources(void); +static void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst); #include "config.h" -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; +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; -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); - usergb = 1; - break; - } - } - - XFree(infos); - - if (!visual) { - visual = DefaultVisual(dpy, screen); - depth = DefaultDepth(dpy, screen); - cmap = DefaultColormap(dpy, screen); - } +static void xinitvisual(); + +static unsigned int +textw_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh) +{ + unsigned int w; + if (startswith(image_prefix, str) && + (w = drw_getimagewidth_clamp(drw, str + strlen(image_prefix), maxw, maxh))) + return MIN(w + lrpad, n); + w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + +static unsigned int +texth_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh) +{ + unsigned int h; + if (startswith(image_prefix, str) && + (h = drw_getimageheight_clamp(drw, str + strlen(image_prefix), maxw, maxh))) + return MIN(h + tbpad, n); + return MIN(bh, n); } 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; +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; +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = mh - 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) + ? texth_clamp(next->text, n, mw - lrpad, image_size) + : textw_clamp(next->text, n, image_size, bh)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) + ? texth_clamp(prev->left->text, n, mw - lrpad, image_size) + : textw_clamp(prev->left->text, n, image_size, bh)) > 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); +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); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); } -static char -*cistrstr(const char *s, const char *sub) { - size_t len; +static char * +cistrstr(const char *h, const char *n) +{ + size_t i; + + if (!n[0]) + return (char *)h; + + for (; *h; ++h) { + for (i = 0; n[i] && tolower((unsigned char)n[i]) == + tolower((unsigned char)h[i]); ++i) + ; + if (n[i] == '\0') + return (char *)h; + } + return NULL; +} - for (len = strlen(sub); *s; s++) - if (!strncasecmp(s, sub, len)) - return (char *)s; - return NULL; +static void +drawhighlights(struct item *item, int x, int y, int maxw) +{ + int i, indent; + char c, *highlight; + + if (!(strlen(item->text) && strlen(text))) + return; + + drw_setscheme(drw, scheme[item == sel + ? SchemeSelHighlight + : SchemeNormHighlight]); + for (i = 0, highlight = item->text; *highlight && text[i];) { + if (!fstrncmp(highlight, &text[i], 1)) { + /* get indentation */ + c = *highlight; + *highlight = '\0'; + indent = TEXTW(item->text); + *highlight = c; + + /* highlight character */ + c = highlight[1]; + highlight[1] = '\0'; + drw_text( + drw, + x + indent - (lrpad / 2.), + y, + MIN(maxw - indent, TEXTW(highlight) - lrpad), + bh, 0, highlight, 0 + ); + highlight[1] = c; + ++i; + } + ++highlight; + } } 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); +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]); + + int vertical = lines > 0; + if (startswith(image_prefix, item->text)) { + char *path = item->text + strlen(image_prefix); + unsigned int image_width = vertical ? w - lrpad : image_size; + unsigned int image_height = vertical ? image_size : bh; + drw_image(drw, &x, &y, &image_width, &image_height, + lrpad, vertical ? tbpad : 0, path, vertical); + if (image_width && image_height) + return vertical ? y : x; + } + int nextx = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + drawhighlights(item, x, y, w); + return vertical ? y + bh : nextx; } static void -drawmenu(void) { - unsigned int curpos; - struct item *item; - int x = 0, y = 0, w; - char *censort; - - 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; - drw_setscheme(drw, scheme[SchemeNorm]); - 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); - - curpos = TEXTW(text) - TEXTW(&text[cursor]); - curpos += lrpad / 2 - 1; - if (using_vi_mode && text[0] != '\0') { - drw_setscheme(drw, scheme[SchemeCursor]); - char vi_char[] = {text[cursor], '\0'}; - drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0); - } else if (using_vi_mode) { - drw_setscheme(drw, scheme[SchemeNorm]); - drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); - } else if (curpos < w) { - drw_setscheme(drw, scheme[SchemeNorm]); - drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); - } - - if (lines > 0) { - /* draw vertical list */ - for (item = curr; item != next; item = item->right) - drawitem(item, x, y += bh, mw - x); - } 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); +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, fh = drw->fonts->h, w; + char *censort; + + 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; + drw_setscheme(drw, scheme[SchemeNorm]); + 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); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + curpos += lrpad / 2 - 1; + if (using_vi_mode && text[0] != '\0') { + drw_setscheme(drw, scheme[SchemeCursor]); + char vi_char[] = {text[cursor], '\0'}; + drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0); + } else if (using_vi_mode) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); + } else if (curpos < w) { + drw_setscheme(drw, scheme[SchemeCaret]); + drw_rect(drw, x + curpos, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + y = bh; + for (item = curr; item != next; item = item->right) + y = drawitem(item, x, y, mw - x); + } 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, textw_clamp(item->text, mw - x - TEXTW(">"), image_size, bh)); + 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); } 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"); +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"); +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 readstdin(FILE* stream); + +static void +refreshoptions() +{ + int dynlen = strlen(dynamic); + int cmdlen = dynlen + 4; + char *cmd; + char *c; + char *t = text; + while (*t) + cmdlen += *t++ == '\'' ? 4 : 1; + cmd = malloc(cmdlen); + if (cmd == NULL) + die("cannot malloc %u bytes:", cmdlen); + strcpy(cmd, dynamic); + t = text; + c = cmd + dynlen; + *(c++) = ' '; + *(c++) = '\''; + while (*t) { + // prefix ' with '\' + if (*t == '\'') { + *(c++) = '\''; + *(c++) = '\\'; + *(c++) = '\''; + } + *(c++) = *(t++); + } + *(c++) = '\''; + *(c++) = 0; + FILE *stream = popen(cmd, "r"); + if (!stream) + die("could not popen dynamic command (%s):", cmd); + readstdin(stream); + int r = pclose(stream); + if (r == -1) + die("could not pclose dynamic command"); + free(cmd); } 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; +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(); +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(); } -static void match(void) { - if (fuzzy) { - fuzzymatch(); - return; - } - static char **tokv = NULL; - static int tokn = 0; - - char buf[sizeof text], *s; - int i, tokc = 0; - size_t len, textsize; - struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; - - strcpy(buf, text); - /* separate input text into tokens to be matched individually */ - for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) - if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) - die("cannot realloc %u bytes:", tokn * sizeof *tokv); - len = tokc ? strlen(tokv[0]) : 0; - - matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; - textsize = strlen(text) + 1; - for (item = items; item && item->text; item++) { - for (i = 0; i < tokc; i++) - if (!fstrstr(item->text, tokv[i])) - break; - if (i != tokc) /* not all tokens match */ - continue; - if (!sortmatches) - appenditem(item, &matches, &matchend); - else { - /* exact matches go first, then prefixes, then substrings */ - if (!tokc || !fstrncmp(text, item->text, textsize)) - appenditem(item, &matches, &matchend); - else if (!fstrncmp(tokv[0], item->text, len)) - appenditem(item, &lprefix, &prefixend); - else - appenditem(item, &lsubstr, &substrend); - } - } - if (lprefix) { - if (matches) { - matchend->right = lprefix; - lprefix->left = matchend; - } else - matches = lprefix; - matchend = prefixend; - } - if (lsubstr) { - if (matches) { - matchend->right = lsubstr; - lsubstr->left = matchend; - } else - matches = lsubstr; - matchend = substrend; - } - curr = sel = matches; - calcoffsets(); +static void +match(void) +{ + if (fuzzy) { + fuzzymatch(); + return; + } + 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 (dynamic) { + refreshoptions(); + matches = matchend = NULL; + for (item = items; item && item->text; item++) + appenditem(item, &matches, &matchend); + curr = sel = matches; + calcoffsets(); + return; + } + + 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; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); } -static void insert(const char *str, ssize_t n) { - if (strlen(text) + n > sizeof text - 1) - return; +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); +} - static char last[BUFSIZ] = ""; - if (reject_no_match) { - /* store last text value in case we need to revert it */ - memcpy(last, text, BUFSIZ); - } +static size_t +nextrune(int inc) +{ + ssize_t n; - /* 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 (!matches && reject_no_match) { - /* revert to last text value if theres no match */ - memcpy(text, last, BUFSIZ); - cursor -= n; - match(); - } + /* 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 size_t nextrune(int inc) { - ssize_t 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); + } +} - /* 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 +loadhistory(void) +{ + FILE *fp = NULL; + static size_t cap = 0; + size_t llen; + char *line; + + if (!histfile) { + return; + } + + fp = fopen(histfile, "r"); + if (!fp) { + return; + } + + for (;;) { + line = NULL; + llen = 0; + if (-1 == getline(&line, &llen, fp)) { + if (ferror(fp)) { + die("failed to read history"); + } + free(line); + break; + } + + if (cap == histsz) { + cap += 64 * sizeof(char*); + history = realloc(history, cap); + if (!history) { + die("failed to realloc memory"); + } + } + strtok(line, "\n"); + history[histsz] = line; + histsz++; + } + histpos = histsz; + + if (fclose(fp)) { + die("failed to close file %s", histfile); + } } -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 +navhistory(int dir) +{ + static char def[BUFSIZ]; + char *p = NULL; + size_t len = 0; + + if (!history || histpos + 1 == 0) + return; + + if (histsz == histpos) { + strncpy(def, text, sizeof(def)); + } + + switch(dir) { + case 1: + if (histpos < histsz - 1) { + p = history[++histpos]; + } else if (histpos == histsz - 1) { + p = def; + histpos++; + } + break; + case -1: + if (histpos > 0) { + p = history[--histpos]; + } + break; + } + if (p == NULL) { + return; + } + + len = MIN(strlen(p), BUFSIZ - 1); + strncpy(text, p, len); + text[len] = '\0'; + cursor = len; + match(); } -static void vi_keypress(KeySym ksym, const XKeyEvent *ev) { - static const size_t quit_len = LENGTH(quit_keys); - if (ev->state & ControlMask) { - switch (ksym) { - /* movement */ - case XK_d: /* fallthrough */ - if (next) { - sel = curr = next; - calcoffsets(); - goto draw; - } else - ksym = XK_G; - break; - case XK_u: - if (prev) { - sel = curr = prev; - calcoffsets(); - goto draw; - } else - ksym = XK_g; - break; - case XK_p: /* fallthrough */ - case XK_P: - break; - case XK_c: - cleanup(); - exit(1); - case XK_Return: /* fallthrough */ - case XK_KP_Enter: - break; - default: - return; - } - } +static void +savehistory(char *input) +{ + unsigned int i; + FILE *fp; + + if (!histfile || + 0 == maxhist || + 0 == strlen(input)) { + goto out; + } + + fp = fopen(histfile, "w"); + if (!fp) { + die("failed to open %s", histfile); + } + for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) { + if (0 >= fprintf(fp, "%s\n", history[i])) { + die("failed to write to %s", histfile); + } + } + if (histsz == 0 || !histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */ + if (0 >= fputs(input, fp)) { + die("failed to write to %s", histfile); + } + } + if (fclose(fp)) { + die("failed to close file %s", histfile); + } + +out: + for (i = 0; i < histsz; i++) { + free(history[i]); + } + free(history); +} - switch (ksym) { - /* movement */ - case XK_0: - cursor = 0; - break; - case XK_dollar: - if (text[cursor + 1] != '\0') { - cursor = strlen(text) - 1; - break; - } - break; - case XK_b: - movewordedge(-1); - break; - case XK_e: - cursor = nextrune(+1); - movewordedge(+1); - if (text[cursor] == '\0') - --cursor; - else - cursor = nextrune(-1); - break; - case XK_g: - if (sel == matches) { - break; - } - sel = curr = matches; - calcoffsets(); - break; - case XK_G: - 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_h: - if (cursor) - cursor = nextrune(-1); - break; - case XK_j: - if (sel && sel->right && (sel = sel->right) == next) { - curr = next; - calcoffsets(); - } - break; - case XK_k: - if (sel && sel->left && (sel = sel->left)->right == curr) { - curr = prev; - calcoffsets(); - } - break; - case XK_l: - if (text[cursor] != '\0' && text[cursor + 1] != '\0') - cursor = nextrune(+1); - else if (text[cursor] == '\0' && cursor) - --cursor; - break; - case XK_w: - movewordedge(+1); - if (text[cursor] != '\0' && text[cursor + 1] != '\0') - cursor = nextrune(+1); - else if (cursor) - --cursor; - break; - /* insertion */ - case XK_a: - cursor = nextrune(+1); - /* fallthrough */ - case XK_i: - using_vi_mode = 0; - break; - case XK_A: - if (text[cursor] != '\0') - cursor = strlen(text); - using_vi_mode = 0; - break; - case XK_I: - cursor = using_vi_mode = 0; - break; - case XK_p: - if (text[cursor] != '\0') - cursor = nextrune(+1); - XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, utf8, - utf8, win, CurrentTime); - return; - case XK_P: - XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, utf8, - utf8, win, CurrentTime); - return; - /* deletion */ - case XK_D: - text[cursor] = '\0'; - if (cursor) - cursor = nextrune(-1); - match(); - break; - case XK_x: - cursor = nextrune(+1); - insert(NULL, nextrune(-1) - cursor); - if (text[cursor] == '\0' && text[0] != '\0') - --cursor; - match(); - break; - /* misc. */ - case XK_Return: - case XK_KP_Enter: - puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); - if (!(ev->state & ControlMask)) { - cleanup(); - exit(0); - } - if (sel) - sel->out = 1; - break; - case XK_Tab: - if (!sel) - return; - strncpy(text, sel->text, sizeof text - 1); - text[sizeof text - 1] = '\0'; - cursor = strlen(text) - 1; - match(); - break; - default: - for (size_t i = 0; i < quit_len; ++i) - if (quit_keys[i].ksym == ksym && - (quit_keys[i].state & ev->state) == quit_keys[i].state) { - cleanup(); - exit(1); - } - } +static void +vi_keypress(KeySym ksym, const XKeyEvent *ev) +{ + static const size_t quit_len = LENGTH(quit_keys); + if (ev->state & ControlMask) { + switch(ksym) { + /* movement */ + case XK_d: /* fallthrough */ + if (next) { + sel = curr = next; + calcoffsets(); + goto draw; + } else + ksym = XK_G; + break; + case XK_u: + if (prev) { + sel = curr = prev; + calcoffsets(); + goto draw; + } else + ksym = XK_g; + break; + case XK_p: /* fallthrough */ + case XK_P: break; + case XK_c: + cleanup(); + exit(1); + case XK_Return: /* fallthrough */ + case XK_KP_Enter: break; + default: return; + } + } + + switch(ksym) { + /* movement */ + case XK_0: + cursor = 0; + break; + case XK_dollar: + if (text[cursor + 1] != '\0') { + cursor = strlen(text) - 1; + break; + } + break; + case XK_b: + movewordedge(-1); + break; + case XK_e: + cursor = nextrune(+1); + movewordedge(+1); + if (text[cursor] == '\0') + --cursor; + else + cursor = nextrune(-1); + break; + case XK_g: + if (sel == matches) { + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_G: + 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_h: + if (cursor) + cursor = nextrune(-1); + break; + case XK_j: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_k: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_l: + if (text[cursor] != '\0' && text[cursor + 1] != '\0') + cursor = nextrune(+1); + else if (text[cursor] == '\0' && cursor) + --cursor; + break; + case XK_w: + movewordedge(+1); + if (text[cursor] != '\0' && text[cursor + 1] != '\0') + cursor = nextrune(+1); + else if (cursor) + --cursor; + break; + /* insertion */ + case XK_a: + cursor = nextrune(+1); + /* fallthrough */ + case XK_i: + using_vi_mode = 0; + break; + case XK_A: + if (text[cursor] != '\0') + cursor = strlen(text); + using_vi_mode = 0; + break; + case XK_I: + cursor = using_vi_mode = 0; + break; + case XK_p: + if (text[cursor] != '\0') + cursor = nextrune(+1); + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_P: + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + /* deletion */ + case XK_D: + text[cursor] = '\0'; + if (cursor) + cursor = nextrune(-1); + match(); + break; + case XK_x: + cursor = nextrune(+1); + insert(NULL, nextrune(-1) - cursor); + if (text[cursor] == '\0' && text[0] != '\0') + --cursor; + match(); + break; + /* misc. */ + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text) - 1; + match(); + break; + default: + for (size_t i = 0; i < quit_len; ++i) + if (quit_keys[i].ksym == ksym && + (quit_keys[i].state & ev->state) == quit_keys[i].state) { + cleanup(); + exit(1); + } + } draw: - drawmenu(); + drawmenu(); } -static void keypress(XKeyEvent *ev) { - char buf[32]; - int len; - 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 (using_vi_mode) { - vi_keypress(ksym, ev); - return; - } else if (vi_mode && (ksym == global_esc.ksym && - (ev->state & global_esc.state) == global_esc.state)) { - using_vi_mode = 1; - if (cursor) - cursor = nextrune(-1); - goto draw; - } - - if (ev->state & ControlMask) { - switch (ksym) { - case XK_a: - ksym = XK_Home; - case XK_b: - break; - 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; - 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: - puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); - if (!(ev->state & ControlMask)) { - cleanup(); - exit(0); - } - if (sel) - sel->out = 1; - break; - case XK_Right: - if (text[cursor] != '\0') { - cursor = nextrune(+1); - break; - } - if (lines > 0) - return; - /* fallthrough */ - case XK_Down: - if (sel && sel->right && (sel = sel->right) == next) { - curr = next; - calcoffsets(); - } - break; - case XK_Tab: - if (!sel) - return; - strncpy(text, sel->text, sizeof text - 1); - text[sizeof text - 1] = '\0'; - cursor = strlen(text); - match(); - break; - } +static void +keypress(XKeyEvent *ev) +{ + char buf[64]; + int len; + KeySym ksym = NoSymbol; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: /* composed string from input method */ + goto insert; + case XLookupKeySym: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ + break; + } + + if (using_vi_mode) { + vi_keypress(ksym, ev); + return; + } else if (vi_mode && + (ksym == global_esc.ksym && + (ev->state & global_esc.state) == global_esc.state)) { + using_vi_mode = 1; + if (cursor) + cursor = nextrune(-1); + goto draw; + } + + 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: + 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: + 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; + case XK_p: + navhistory(-1); + buf[0]=0; + break; + case XK_n: + navhistory(1); + buf[0]=0; + break; + 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 (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 (!(ev->state & ControlMask)) { + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + savehistory((sel && !(ev->state & ShiftMask)) + ? sel->text : text); + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + case XK_KP_Right: + 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 (!sel) + return; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + match(); + break; + } draw: - drawmenu(); + drawmenu(); } -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; +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; + /* disabled below, needs to be fixed */ + /* + 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; + } + } +} - /* 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; - } - } +static void +mousemove(XEvent *e) +{ + struct item *item; + XPointerMovedEvent *ev = &e->xmotion; + int x = 0, y = 0, h = bh, w; + + if (lines > 0) { + w = mw - x; + for (item = curr; item != next; item = item->right) { + y += h; + if (ev->y >= y && ev->y <= (y + h)) { + sel = item; + calcoffsets(); + drawmenu(); + return; + } + } + } else if (matches) { + x += inputw + promptw; + w = TEXTW("<"); + 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) { + sel = item; + calcoffsets(); + drawmenu(); + return; + } + } + } } -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); - } - if (using_vi_mode && text[cursor] == '\0') - --cursor; - 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); + } + if (using_vi_mode && text[cursor] == '\0') + --cursor; + drawmenu(); } -static void readstdin(void) { - char buf[sizeof text], *p; - size_t i, imax = 0, size = 0; - unsigned int tmpmax = 0; +static void +readstdin(FILE* stream) +{ + char *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; - if (passwd) { + if(passwd){ inputw = lines = 0; return; } - /* 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); + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &linesiz, stream)) != -1; i++) { + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) + die("strdup:"); + + items[i].out = 0; + } + free(line); + if (items) + items[i].text = NULL; + lines = MIN(max_lines, i); } -static void run(void) { - XEvent ev; - - while (!XNextEvent(dpy, &ev)) { - if (XFilterEvent(&ev, win)) - continue; - switch (ev.type) { - case ButtonPress: - buttonpress(&ev); - break; - 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; - } - } +void +resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) +{ + char *sdst = NULL; + int *idst = NULL; + float *fdst = NULL; + sdst = dst; + idst = dst; + fdst = dst; + char fullname[256]; + char *type; + XrmValue ret; + snprintf(fullname, sizeof(fullname), "%s.%s", "dmenu", name); + fullname[sizeof(fullname) - 1] = '\0'; + XrmGetResource(db, fullname, "*", &type, &ret); + if (!(ret.addr == NULL || strncmp("String", type, 64))) + { + switch (rtype) { + case STRING: + strcpy(sdst, ret.addr); + break; + case INTEGER: + *idst = strtoul(ret.addr, NULL, 10); + break; + case FLOAT: + *fdst = strtof(ret.addr, NULL); + 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], alphas[j], 2); +void +load_xresources(void) +{ + Display *display; + char *resm; + XrmDatabase db; + ResourcePref *p; + display = XOpenDisplay(NULL); + resm = XResourceManagerString(display); + if (!resm) + return; + db = XrmGetStringDatabase(resm); + for (p = resources; p < resources + LENGTH(resources); p++) + resource_load(db, p->name, p->type, p->dst); + XCloseDisplay(display); +} - clip = XInternAtom(dpy, "CLIPBOARD", False); - utf8 = XInternAtom(dpy, "UTF8_STRING", False); +static void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case ButtonPress: + buttonpress(&ev); + break; + case MotionNotify: + mousemove(&ev); + break; + 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; + } + } +} - /* calculate menu geometry */ - bh = drw->fonts->h + 2; - lines = MAX(lines, 0); - mh = (lines + 1) * bh; +static void +setup(void) +{ + int x, y, i = 0, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; #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; - - 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); - XFree(info); - } else + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; #endif - { - if (!XGetWindowAttributes(dpy, parentwin, &wa)) - die("could not get embedding window attributes: 0x%lx", parentwin); - x = dmx; - y = topbar ? dmy : wa.height - mh - dmy; - mw = (dmw > 0 ? dmw : wa.width); - } - promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; - inputw = MIN(inputw, mw / 3); - match(); - - /* create menu window */ - swa.override_redirect = True; - swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; - swa.border_pixel = 0; - swa.colormap = cmap; - swa.event_mask = - ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask; - win = - XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, depth, InputOutput, visual, - CWOverrideRedirect | CWBackPixel | CWColormap | - CWEventMask | CWBorderPixel, - &swa); - XSetClassHint(dpy, win, &ch); - - /* 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(); + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], alphas[i], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ + lines = MAX(lines, 0); + /* default values for image_size */ + if (image_size < 0) + image_size = (lines > 0) ? 2 * bh : 8 * bh; + mh = bh + ((lines > 0) ? MAX(lines * bh, image_size) : 0); +#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; + + 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);; + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + x = dmx; + y = topbar ? dmy : wa.height - mh - dmy; + mw = (dmw>0 ? dmw : wa.width); + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = mw / 3; /* input width: ~33% of monitor width */ + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = 0; + swa.border_pixel = 0; + swa.colormap = cmap; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | + ButtonPressMask | PointerMotionMask; + win = XCreateWindow(dpy, root, x, y, mw - (2 * border_width), mh, border_width, + depth, CopyFromParent, visual, + CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &swa); + if (border_width) + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); + + /* 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) { + XReparentWindow(dpy, win, parentwin, x, y); + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); } -static void usage(void) { - fputs( - "usage: dmenu [-bfiPrvS] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" +static void +usage(void) +{ + die("usage: dmenu [-bFfivP] [-l lines] [-h height] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color]\n" " [-x xoffset] [-y yoffset] [-z width]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-w " - "windowid]\n", - stderr); - exit(1); -} - -void read_Xresources(void) { - XrmInitialize(); - - char *xrm; - if ((xrm = XResourceManagerString(drw->dpy))) { - char *type; - XrmDatabase xdb = XrmGetStringDatabase(xrm); - XrmValue xval; - - if (XrmGetResource(xdb, "dmenu.font", "*", &type, &xval) == - True) /* font or font set */ - fonts[0] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == - True) /* normal background color */ - colors[SchemeNorm][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == - True) /* normal foreground color */ - colors[SchemeNorm][ColFg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.color4", "*", &type, &xval) == - True) /* selected background color */ - colors[SchemeSel][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dmenu.color0", "*", &type, &xval) == - True) /* selected foreground color */ - colors[SchemeSel][ColFg] = strdup(xval.addr); - - XrmDestroyDatabase(xdb); - } + " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]\n" + " [-ip image_prefix] [-is image_size] [-H histfile] [-dy command]\n", stderr); } -int main(int argc, char *argv[]) { - XWindowAttributes wa; - int i, fast = 0; - - 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); - xinitvisual(); - drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); - read_Xresources(); - - for (i = 1; i < argc; i++) - /* these options take no arguments */ - if (!strcmp(argv[i], "-v")) { /* prints version information */ - puts("dmenu-" VERSION); - exit(0); - } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ - topbar = 0; - else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ - fast = 1; - else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */ - fuzzy = 0; - else if (!strcmp(argv[i], "-S")) /* do not sort matches */ - sortmatches = false; - else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ - fstrncmp = strncasecmp; - fstrstr = cistrstr; - } else if (!strcmp(argv[i], "-vi")) { - vi_mode = 1; - using_vi_mode = start_mode; - global_esc.ksym = XK_Escape; - global_esc.state = 0; - } else if (!strcmp(argv[i], "-P")) /* is the input a password */ - passwd = 1; - else if (!strcmp(argv[i], - "-r")) /* reject input which results in no match */ - reject_no_match = 1; - else if (i + 1 == argc) - usage(); - /* these options take one argument */ - else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ - lines = atoi(argv[++i]); - else if (!strcmp(argv[i], "-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], "-z")) /* make dmenu this wide */ - dmw = atoi(argv[++i]); - else if (!strcmp(argv[i], "-m")) - mon = atoi(argv[++i]); - else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ - prompt = argv[++i]; - else if (!strcmp(argv[i], "-fn")) /* font or font set */ - fonts[0] = argv[++i]; - else if (!strcmp(argv[i], "-nb")) /* normal background color */ - 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]; - else - usage(); - - if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) - die("no fonts could be loaded."); - lrpad = drw->fonts->h; +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + XrmInitialize(); + load_xresources(); + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-F")) /* disables fuzzy matching */ + fuzzy = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (!strcmp(argv[i], "-vi")) { + vi_mode = 1; + using_vi_mode = start_mode; + global_esc.ksym = XK_Escape; + global_esc.state = 0; + } else if (!strcmp(argv[i], "-P")) /* is the input a password */ + passwd = 1; + else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-H")) + histfile = argv[++i]; + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight, min_lineheight); + } + 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], "-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], "-z")) /* make dmenu this wide */ + dmw = atoi(argv[++i]); + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + 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], "-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]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else if (!strcmp(argv[i], "-bw")) + border_width = atoi(argv[++i]); /* border width */ + else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */ + dynamic = argv[++i] && *argv[i] ? argv[i] : NULL; + else if (!strcmp(argv[i], "-ip")) /* image prefix */ + image_prefix = argv[++i]; + else if (!strcmp(argv[i], "-is")) /* max. image preview size (height or width) */ + image_size = atoi(argv[++i]); + 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); + xinitvisual(); + drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + tbpad = lrpad / 2; #ifdef __OpenBSD__ - if (pledge("stdio rpath", NULL) == -1) - die("pledge"); + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); #endif - if (fast && !isatty(0)) { - grabkeyboard(); - readstdin(); - } else { - readstdin(); - grabkeyboard(); - } - setup(); - run(); + loadhistory(); + + max_lines = lines; + if (fast && !isatty(0)) { + grabkeyboard(); + if (!dynamic) + readstdin(stdin); + } else { + if (!dynamic) + readstdin(stdin); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} - return 1; /* unreachable */ +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) { + visual = DefaultVisual(dpy, screen); + depth = DefaultDepth(dpy, screen); + cmap = DefaultColormap(dpy, screen); + } } diff --git a/dmenu/dmenu_path b/dmenu/dmenu_path index 3a7cda7..9b3b1f2 100755 --- a/dmenu/dmenu_path +++ b/dmenu/dmenu_path @@ -1,13 +1,12 @@ #!/bin/sh +# +# dmenu_path: Override dmenu_path sorting results by atime. +# +# By default, dmenu_path sorts executables alphabetically. It seems to make +# more sense to sort them by atime in an effort to reduce the number of +# keystrokes needed to start a program. -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 +echo $PATH | tr ':' '\n' | uniq | sed 's#$#/#' | # List directories in $PATH + xargs ls -lu --time-style=+%s | # Add atime epoch + awk '/^(-|l)/ { print $6, $7 }' | # Only print timestamp and name + sort -rn | cut -d' ' -f 2 diff --git a/dmenu/dmenu_path_desktop b/dmenu/dmenu_path_desktop new file mode 100755 index 0000000..5ff74b3 --- /dev/null +++ b/dmenu/dmenu_path_desktop @@ -0,0 +1,18 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run_desktop" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +dirs="" +IFS=: +for dir in "${XDG_DATA_DIRS:-"/usr/local/share:/usr/share"}"; do + dirs="${dirs}:${dir}/applications" +done; + +if stest -dqr -n "$cache" $PATH; then + stest -fl $dirs | grep -v 'mimeinfo.cache' | sed 's/\.[^./]*$//' | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu/dmenu_run b/dmenu/dmenu_run index 71a15d7..a9e23b6 100755 --- a/dmenu/dmenu_run +++ b/dmenu/dmenu_run @@ -1,3 +1,14 @@ #!/bin/sh -LANG="en_US.UTF-8" -dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & + +# dmenu_run improved +# command ending with '!', is started in the terminal. + +test -s "$HOME"/.dmenurc && . "$HOME"/.dmenurc + +cmd="$(dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@")" + +case $cmd in +'') ;; +*\;) exec "${TERMINAL:-st}" -e ${cmd%?} & ;; +*) exec ${cmd} & ;; +esac diff --git a/dmenu/dmenu_run_desktop b/dmenu/dmenu_run_desktop new file mode 100755 index 0000000..a90aceb --- /dev/null +++ b/dmenu/dmenu_run_desktop @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path_desktop | dmenu "$@" | xargs gtk-launch & diff --git a/dmenu/drw.c b/dmenu/drw.c index ac1d696..0c5b84f 100644 --- a/dmenu/drw.c +++ b/dmenu/drw.c @@ -4,59 +4,57 @@ #include <string.h> #include <X11/Xlib.h> #include <X11/Xft/Xft.h> +#include <spng.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}; +struct image_item { + const char *path; + int width; + int height; + char *buf; + Pixmap pixmap; + struct image_item *next; +}; -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 struct image_item *images = NULL; -static size_t -utf8decode(const char *c, long *u, size_t clen) +static int +utf8decode(const char *s_in, long *u, int *err) { - size_t i, j, len, type; - long udecoded; - + static const unsigned char lens[] = { + /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ + /* 110XX */ 2, 2, 2, 2, + /* 1110X */ 3, 3, + /* 11110 */ 4, + /* 11111 */ 0, /* invalid */ + }; + static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; + static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; + + const unsigned char *s = (const unsigned char *)s_in; + int len = lens[*s >> 3]; *u = UTF_INVALID; - if (!clen) - return 0; - udecoded = utf8decodebyte(c[0], &len); - if (!BETWEEN(len, 1, UTF_SIZ)) + *err = 1; + if (len == 0) return 1; - for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { - udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); - if (type) - return j; + + long cp = s[0] & leading_mask[len - 1]; + for (int i = 1; i < len; ++i) { + if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) + return i; + cp = (cp << 6) | (s[i] & 0x3F); } - if (j < len) - return 0; - *u = udecoded; - utf8validate(u, len); + /* out of range, surrogate, overlong encoding */ + if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) + return len; + *err = 0; + *u = cp; return len; } @@ -192,13 +190,14 @@ drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha) if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, clrname, dest)) die("error, cannot allocate color '%s'", clrname); - dest->pixel = (dest->pixel & 0x00FFFFFFFU) | alpha << 24; + + dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24); } /* 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[], const unsigned int alphas[], size_t clrcount) +drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount) { size_t i; Clr *ret; @@ -241,47 +240,72 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int 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; + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; - size_t i, len; - int utf8strlen, utf8charlen, render = x || y || w || h; + int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; - int charexists = 0; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width, invalid_width; + static const char invalid[] = "�"; - if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) return 0; if (!render) { - w = ~w; + w = invert ? invert : ~invert; } else { XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + if (w < lpad) + return x + w; d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); x += lpad; w -= lpad; } usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + if (!invalid_width && render) + invalid_width = drw_fontset_getwidth(drw, invalid); while (1) { - utf8strlen = 0; + ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { - utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); for (curfont = drw->fonts; curfont; curfont = curfont->next) { charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { - if (curfont == usedfont) { - utf8strlen += utf8charlen; + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { text += utf8charlen; + utf8strlen += utf8err ? 0 : utf8charlen; + ew += utf8err ? 0 : tmpw; } else { nextfont = curfont; } @@ -289,36 +313,31 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp } } - if (!charexists || nextfont) + if (overflow || !charexists || nextfont || utf8err) 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 (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); } + x += ew; + w -= ew; } + if (utf8err && (!render || invalid_width < w)) { + if (render) + drw_text(drw, x, y, w, h, 0, invalid, invert); + x += invalid_width; + w -= invalid_width; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); - if (!*text) { + if (!*text || overflow) { break; } else if (nextfont) { charexists = 0; @@ -328,6 +347,15 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp * character must be drawn. */ charexists = 1; + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); @@ -355,6 +383,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp curfont->next = usedfont; } else { xfont_free(usedfont); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: usedfont = drw->fonts; } } @@ -366,6 +396,163 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp return x + (render ? w : 0); } +static struct image_item * +load_image(Drw *drw, unsigned int maxw, unsigned int maxh, const char *path) +{ + FILE *png; + spng_ctx *ctx = NULL; + int ret = 0; + struct spng_ihdr ihdr; + struct spng_plte plte = {0}; + struct spng_row_info row_info = {0}; + char *spng_buf; + int fmt = SPNG_FMT_RGBA8; + int crop_width; + int crop_height; + + struct image_item *image = ecalloc(1, sizeof(struct image_item)); + image->path = path; + image->next = images; + images = image; + + png = fopen(path, "rb"); + if (png == NULL) { + fprintf(stderr, "error opening input file %s\n", path); + return NULL; + } + + /* Create a context */ + ctx = spng_ctx_new(0); + if (ctx == NULL) { + fprintf(stderr, "%s: spng_ctx_new() failed\n", path); + return NULL; + } + + /* Ignore and don't calculate chunk CRC's */ + spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); + + /* Set memory usage limits for storing standard and unknown chunks, + this is important when reading untrusted files! */ + size_t limit = 1024 * 1024 * 64; + spng_set_chunk_limits(ctx, limit, limit); + + spng_set_png_file(ctx, png); + + ret = spng_get_ihdr(ctx, &ihdr); + if (ret) { + fprintf(stderr, "%s: spng_get_ihdr() error: %s\n", path, spng_strerror(ret)); + return NULL; + } + + ret = spng_get_plte(ctx, &plte); + if (ret && ret != SPNG_ECHUNKAVAIL) { + fprintf(stderr, "%s: spng_get_plte() error: %s\n", path, spng_strerror(ret)); + return NULL; + } + + size_t image_size, bytes_per_row; /* size in bytes, not in pixels */ + + ret = spng_decoded_image_size(ctx, fmt, &image_size); + if (ret) + return NULL; + + spng_buf = malloc(image_size); + if (!spng_buf) + return NULL; + + ret = spng_decode_image(ctx, NULL, 0, fmt, SPNG_DECODE_PROGRESSIVE); + if (ret) { + fprintf(stderr, "%s: progressive spng_decode_image() error: %s\n", + path, spng_strerror(ret)); + return NULL; + } + + /* ihdr.height will always be non-zero if spng_get_ihdr() succeeds */ + bytes_per_row = image_size / ihdr.height; + crop_width = MIN(ihdr.width, maxw); + crop_height = MIN(ihdr.height, maxh); + + do { + ret = spng_get_row_info(ctx, &row_info); + if (ret) + break; + ret = spng_decode_row(ctx, spng_buf + row_info.row_num * bytes_per_row, bytes_per_row); + } while (!ret && row_info.row_num < crop_height); + + if (ret != SPNG_EOI && row_info.row_num < crop_height) + fprintf(stderr, "%s: progressive decode error: %s\n", path, spng_strerror(ret)); + + image->buf = calloc(ihdr.width * crop_height * 4, sizeof(char)); + for (int i = 0; i < ihdr.width * crop_height; i++) { + /* RGBA to BGRA */ + image->buf[i*4+2] = spng_buf[i*4+0]; + image->buf[i*4+1] = spng_buf[i*4+1]; + image->buf[i*4+0] = spng_buf[i*4+2]; + image->buf[i*4+3] = spng_buf[i*4+3]; + } + image->width = crop_width; + image->height = crop_height; + + XImage *img = XCreateImage(drw->dpy, CopyFromParent, DefaultDepth(drw->dpy, drw->screen), + ZPixmap, 0, image->buf, ihdr.width, crop_height, 32, 0); + image->pixmap = XCreatePixmap(drw->dpy, drw->root, crop_width, crop_height, 24); + XPutImage(drw->dpy, image->pixmap, drw->gc, img, 0, 0, 0, 0, crop_width, crop_height); + spng_ctx_free(ctx); + fclose(png); + return image; +} + +void +drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h, + unsigned int lrpad, unsigned int tbpad, const char *path, int vertical) +{ + /* *x and *y refer to box position including padding, + * *w and *h are the maximum image width and height without padding */ + struct image_item *image = NULL; + int render = *x || *y; + int crop_width, crop_height; + + // find path in images + for (struct image_item *item = images; item != NULL; item = item->next) { + if (!strcmp(item->path, path)) { + image = item; + if (!image->buf) + goto file_error; + break; + } + } + + if (!image && !(image = load_image(drw, *w, *h, path))) + goto file_error; + + if (!render) { + *w = image->width; + *h = image->height; + return; + } + + crop_width = MIN(image->width, *w); + crop_height = MIN(image->height, *h); + if (vertical) + *h = crop_height; + else + *w = crop_width; + + XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, *x, *y, *w + lrpad, *h + tbpad); + XCopyArea(drw->dpy, image->pixmap, drw->drawable, drw->gc, 0, 0, + crop_width, crop_height, *x + lrpad/2, *y + tbpad/2); + + if (vertical) + *y += *h + tbpad; + else + *x += *w + lrpad; + return; + +file_error: + *w = *h = 0; +} + void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) { @@ -384,6 +571,15 @@ drw_fontset_getwidth(Drw *drw, const char *text) return drw_text(drw, 0, 0, 0, 0, 0, text, 0); } +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) { @@ -399,6 +595,26 @@ drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, *h = font->h; } +unsigned int +drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh) +{ + int x = 0, y = 0; + unsigned int w = maxw, h = maxh; + if (drw && path && maxw && maxh) + drw_image(drw, &x, &y, &w, &h, 0, 0, path, 0); + return MIN(maxw, w); +} + +unsigned int +drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh) +{ + int x = 0, y = 0; + unsigned int w = maxw, h = maxh; + if (drw && path && maxw && maxh) + drw_image(drw, &x, &y, &w, &h, 0, 0, path, 1); + return MIN(maxh, h); +} + Cur * drw_cur_create(Drw *drw, int shape) { @@ -421,3 +637,4 @@ drw_cur_free(Drw *drw, Cur *cursor) XFreeCursor(drw->dpy, cursor->cursor); free(cursor); } + diff --git a/dmenu/drw.h b/dmenu/drw.h index 4f66f0d..c6a1a9b 100644 --- a/dmenu/drw.h +++ b/dmenu/drw.h @@ -30,7 +30,7 @@ typedef struct { } Drw; /* Drawable abstraction */ -Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap); +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual*, unsigned int, Colormap); void drw_resize(Drw *drw, unsigned int w, unsigned int h); void drw_free(Drw *drw); @@ -38,11 +38,16 @@ void drw_free(Drw *drw); 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); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); +/* Image abstraction */ +unsigned int drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh); +unsigned int drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh); + /* Colorscheme abstraction */ void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha); -Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount); +Clr *drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount); /* Cursor abstraction */ Cur *drw_cur_create(Drw *drw, int shape); @@ -55,6 +60,8 @@ 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); +void drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h, unsigned int lrpad, unsigned int tbpad, const char *path, int vertical); /* Map functions */ void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); + diff --git a/dmenu/patches/dmenu-alpha-20230110-5.2.diff b/dmenu/patches/dmenu-alpha-20230110-5.2.diff new file mode 100644 index 0000000..e3b1493 --- /dev/null +++ b/dmenu/patches/dmenu-alpha-20230110-5.2.diff @@ -0,0 +1,293 @@ +From 4709ed81c8b8df043420ca9de016054088beb934 Mon Sep 17 00:00:00 2001 +From: Andrew Slice <edward.andrew.slice@gmail.com> +Date: Tue, 10 Jan 2023 17:22:44 -0500 +Subject: [PATCH] Adds alpha transparency. This also fixes a crash that happens + when using '-w' to embed dmenu in a window that has alpha transparency. + +Based on the original patch by Marcin Lukow <marcin@nerdy.cat> +--- + config.def.h | 7 ++++++ + config.mk | 2 +- + dmenu.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++----- + drw.c | 26 ++++++++++++----------- + drw.h | 9 +++++--- + 5 files changed, 83 insertions(+), 21 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..809c96e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,7 @@ + /* Default settings; can be overriden by command line. */ + + static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ ++static const unsigned int alpha = 0xff; /* Amount of opacity. 0xff is opaque */ + /* -fn option overrides fonts[0]; default X11 font or font set */ + static const char *fonts[] = { + "monospace:size=10" +@@ -13,6 +14,12 @@ static const char *colors[SchemeLast][2] = { + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, + }; ++ ++static const unsigned int alphas[SchemeLast][2] = { ++ [SchemeNorm] = { OPAQUE, alpha }, ++ [SchemeSel] = { OPAQUE, alpha }, ++ [SchemeOut] = { OPAQUE, alpha }, ++}; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; + +diff --git a/config.mk b/config.mk +index 566348b..fa2b4fc 100644 +--- a/config.mk ++++ b/config.mk +@@ -21,7 +21,7 @@ FREETYPEINC = /usr/include/freetype2 + + # includes and libs + INCS = -I$(X11INC) -I$(FREETYPEINC) +-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) ++LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +diff --git a/dmenu.c b/dmenu.c +index 27b7a30..a20302f 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -10,10 +10,12 @@ + + #include <X11/Xlib.h> + #include <X11/Xatom.h> ++#include <X11/Xproto.h> + #include <X11/Xutil.h> + #ifdef XINERAMA + #include <X11/extensions/Xinerama.h> + #endif ++#include <X11/extensions/Xrender.h> + #include <X11/Xft/Xft.h> + + #include "drw.h" +@@ -25,6 +27,8 @@ + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + ++#define OPAQUE 0xffu ++ + /* enums */ + enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +@@ -53,10 +57,16 @@ static XIC xic; + static Drw *drw; + static Clr *scheme[SchemeLast]; + ++static int useargb = 0; ++static Visual *visual; ++static int depth; ++static Colormap cmap; ++ + #include "config.h" + + static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; + static char *(*fstrstr)(const char *, const char *) = strstr; ++static void xinitvisual(); + + static unsigned int + textw_clamp(const char *str, unsigned int n) +@@ -627,7 +637,7 @@ setup(void) + #endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) +- scheme[j] = drw_scm_create(drw, colors[j], 2); ++ scheme[j] = drw_scm_create(drw, colors[j], alphas[i], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); +@@ -682,11 +692,13 @@ setup(void) + + /* create menu window */ + swa.override_redirect = True; +- swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ swa.background_pixel = 0; ++ swa.border_pixel = 0; ++ swa.colormap = cmap; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, +- CopyFromParent, CopyFromParent, CopyFromParent, +- CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); ++ depth, CopyFromParent, visual, ++ CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + +@@ -771,7 +783,8 @@ main(int argc, char *argv[]) + 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); ++ xinitvisual(); ++ drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; +@@ -793,3 +806,40 @@ main(int argc, char *argv[]) + + return 1; /* unreachable */ + } ++ ++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) { ++ visual = DefaultVisual(dpy, screen); ++ depth = DefaultDepth(dpy, screen); ++ cmap = DefaultColormap(dpy, screen); ++ } ++} +diff --git a/drw.c b/drw.c +index a58a2b4..42700e5 100644 +--- a/drw.c ++++ b/drw.c +@@ -61,7 +61,7 @@ utf8decode(const char *c, long *u, size_t clen) + } + + Drw * +-drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) ++drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap) + { + Drw *drw = ecalloc(1, sizeof(Drw)); + +@@ -70,8 +70,11 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h + drw->root = root; + drw->w = w; + drw->h = h; +- drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); +- drw->gc = XCreateGC(dpy, root, 0, NULL); ++ drw->visual = visual; ++ drw->depth = depth; ++ drw->cmap = cmap; ++ drw->drawable = XCreatePixmap(dpy, root, w, h, depth); ++ drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +@@ -87,7 +90,7 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h) + 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)); ++ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth); + } + + void +@@ -181,21 +184,22 @@ drw_fontset_free(Fnt *font) + } + + void +-drw_clr_create(Drw *drw, Clr *dest, const char *clrname) ++drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha) + { + if (!drw || !dest || !clrname) + return; + +- if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), +- DefaultColormap(drw->dpy, drw->screen), ++ if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap, + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); ++ ++ dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24); + } + + /* 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) ++drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount) + { + size_t i; + Clr *ret; +@@ -205,7 +209,7 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) + return NULL; + + for (i = 0; i < clrcount; i++) +- drw_clr_create(drw, &ret[i], clrnames[i]); ++ drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]); + return ret; + } + +@@ -263,9 +267,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp + } 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)); ++ d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap); + x += lpad; + w -= lpad; + } +diff --git a/drw.h b/drw.h +index fd7631b..48f2f93 100644 +--- a/drw.h ++++ b/drw.h +@@ -20,6 +20,9 @@ typedef struct { + Display *dpy; + int screen; + Window root; ++ Visual *visual; ++ unsigned int depth; ++ Colormap cmap; + Drawable drawable; + GC gc; + Clr *scheme; +@@ -27,7 +30,7 @@ typedef struct { + } Drw; + + /* Drawable abstraction */ +-Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); ++Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual*, unsigned int, Colormap); + void drw_resize(Drw *drw, unsigned int w, unsigned int h); + void drw_free(Drw *drw); + +@@ -39,8 +42,8 @@ unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int + 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); ++void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha); ++Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount); + + /* Cursor abstraction */ + Cur *drw_cur_create(Drw *drw, int shape); +-- +2.37.4 + diff --git a/dmenu/patches/dmenu-border-20230512-0fe460d.diff b/dmenu/patches/dmenu-border-20230512-0fe460d.diff new file mode 100644 index 0000000..f7a5971 --- /dev/null +++ b/dmenu/patches/dmenu-border-20230512-0fe460d.diff @@ -0,0 +1,36 @@ +diff --git a/config.def.h b/config.def.h +index 1edb647..dd3eb31 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,3 +21,6 @@ static unsigned int lines = 0; + * for example: " /?\"&[]" + */ + static const char worddelimiters[] = " "; ++ ++/* Size of the window border */ ++static unsigned int border_width = 0; +diff --git a/dmenu.c b/dmenu.c +index 27b7a30..7c130fc 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -684,9 +684,11 @@ setup(void) + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; +- win = XCreateWindow(dpy, root, x, y, mw, mh, 0, ++ win = XCreateWindow(dpy, root, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); ++ if (border_width) ++ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); + + +@@ -757,6 +759,8 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; ++ else if (!strcmp(argv[i], "-bw")) ++ border_width = atoi(argv[++i]); /* border width */ + else + usage(); diff --git a/dmenu/patches/dmenu-colored-caret-5.2.diff b/dmenu/patches/dmenu-colored-caret-5.2.diff new file mode 100644 index 0000000..0482b35 --- /dev/null +++ b/dmenu/patches/dmenu-colored-caret-5.2.diff @@ -0,0 +1,38 @@ + config.def.h | 1 + + dmenu.c | 4 ++-- + 3 files changed, 4 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..b41a8f0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,6 +12,7 @@ static const char *colors[SchemeLast][2] = { + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, ++ [SchemeCaret] = { "#eeeeee", "#222222" }, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; + +diff --git a/dmenu.c b/dmenu.c +index 98507d9..fd916d7 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -26,7 +26,7 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeOut, SchemeCaret, SchemeLast }; /* color schemes */ + + struct item { + char *text; +@@ -147,7 +147,7 @@ drawmenu(void) + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { +- drw_setscheme(drw, scheme[SchemeNorm]); ++ drw_setscheme(drw, scheme[SchemeCaret]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } diff --git a/dmenu/patches/dmenu-desktoponly-20240811-475d809.diff b/dmenu/patches/dmenu-desktoponly-20240811-475d809.diff new file mode 100644 index 0000000..bc6698e --- /dev/null +++ b/dmenu/patches/dmenu-desktoponly-20240811-475d809.diff @@ -0,0 +1,86 @@ +From fada397413584331798a1a9b8703a10110450889 Mon Sep 17 00:00:00 2001 +From: Robert Bilski <robert@rbilski.com> +Date: Sun, 4 Aug 2024 22:07:35 +0200 +Subject: [PATCH] Handle .desktop files + +Retrieve files from applications/ and open them using gtk-launch +--- + Makefile | 8 ++++++-- + dmenu_path_desktop | 18 ++++++++++++++++++ + dmenu_run_desktop | 2 ++ + 3 files changed, 26 insertions(+), 2 deletions(-) + create mode 100755 dmenu_path_desktop + create mode 100755 dmenu_run_desktop + +diff --git a/Makefile b/Makefile +index 458c524..f157bb1 100644 +--- a/Makefile ++++ b/Makefile +@@ -28,7 +28,7 @@ clean: + 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)\ ++ drw.h util.h dmenu_path dmenu_path_desktop dmenu_run dmenu_run_desktop stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar +@@ -36,10 +36,12 @@ dist: clean + + install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin +- cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin ++ cp -f dmenu dmenu_path dmenu_path_desktop dmenu_run dmenu_run_desktop stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path ++ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path_desktop + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run ++ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run_desktop + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 +@@ -50,7 +52,9 @@ install: all + uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ ++ $(DESTDIR)$(PREFIX)/bin/dmenu_path_desktop\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ ++ $(DESTDIR)$(PREFIX)/bin/dmenu_run_desktop\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 +diff --git a/dmenu_path_desktop b/dmenu_path_desktop +new file mode 100755 +index 0000000..5ff74b3 +--- /dev/null ++++ b/dmenu_path_desktop +@@ -0,0 +1,18 @@ ++#!/bin/sh ++ ++cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" ++cache="$cachedir/dmenu_run_desktop" ++ ++[ ! -e "$cachedir" ] && mkdir -p "$cachedir" ++ ++dirs="" ++IFS=: ++for dir in "${XDG_DATA_DIRS:-"/usr/local/share:/usr/share"}"; do ++ dirs="${dirs}:${dir}/applications" ++done; ++ ++if stest -dqr -n "$cache" $PATH; then ++ stest -fl $dirs | grep -v 'mimeinfo.cache' | sed 's/\.[^./]*$//' | sort -u | tee "$cache" ++else ++ cat "$cache" ++fi +diff --git a/dmenu_run_desktop b/dmenu_run_desktop +new file mode 100755 +index 0000000..a90aceb +--- /dev/null ++++ b/dmenu_run_desktop +@@ -0,0 +1,2 @@ ++#!/bin/sh ++dmenu_path_desktop | dmenu "$@" | xargs gtk-launch & +-- +2.46.0 + diff --git a/dmenu/patches/dmenu-dynamicoptions-5.0.diff b/dmenu/patches/dmenu-dynamicoptions-5.0.diff new file mode 100644 index 0000000..e26f3bd --- /dev/null +++ b/dmenu/patches/dmenu-dynamicoptions-5.0.diff @@ -0,0 +1,195 @@ +From f70735c476c25da46f9e44b835ac967e0dfa4d85 Mon Sep 17 00:00:00 2001 +From: Ziad EL KHOURY HANNA <me@ziadkh.ovh> +Date: Mon, 29 Nov 2021 17:36:35 +0100 +Subject: [PATCH] add `-dy` flag for dynamic menu updating + +`-dy` flag makes dmenu run the command given to it whenever input is +changed with the current input as the last argument and update the +option list according to the output of that command. + +Based on dynamic options patch by ttmx <tiago.sequeira.teles@gmail.com>. +Adds proper quoting of the given command. +Adds option to man file documentation. +--- + config.def.h | 1 + + dmenu.1 | 5 ++++ + dmenu.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++----- + 3 files changed, 70 insertions(+), 6 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..035b877 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ static const char *fonts[] = { + "monospace:size=10" + }; + static const char *prompt = NULL; /* -p option; prompt to the left of input field */ ++static const char *dynamic = NULL; /* -dy option; dynamic command to run on input change */ + static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..1ae3fe3 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -22,6 +22,8 @@ dmenu \- dynamic menu + .IR color ] + .RB [ \-w + .IR windowid ] ++.RB [ \-dy ++.IR command ] + .P + .BR dmenu_run " ..." + .SH DESCRIPTION +@@ -80,6 +82,9 @@ prints version information to stdout, then exits. + .TP + .BI \-w " windowid" + embed into windowid. ++.TP ++.BI \-dy " command" ++runs command whenever input changes to update menu items. + .SH USAGE + dmenu is completely controlled by the keyboard. Items are selected using the + arrow keys, page up, page down, home, and end. +diff --git a/dmenu.c b/dmenu.c +index 65f25ce..6780122 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -44,6 +44,7 @@ static struct item *items = NULL; + static struct item *matches, *matchend; + static struct item *prev, *curr, *next, *sel; + static int mon = -1, screen; ++static unsigned int max_lines = 0; + + static Atom clip, utf8; + static Display *dpy; +@@ -210,6 +211,47 @@ grabkeyboard(void) + die("cannot grab keyboard"); + } + ++static void readstdin(FILE* stream); ++ ++static void ++refreshoptions() ++{ ++ int dynlen = strlen(dynamic); ++ int cmdlen = dynlen + 4; ++ char *cmd; ++ char *c; ++ char *t = text; ++ while (*t) ++ cmdlen += *t++ == '\'' ? 4 : 1; ++ cmd = malloc(cmdlen); ++ if (cmd == NULL) ++ die("cannot malloc %u bytes:", cmdlen); ++ strcpy(cmd, dynamic); ++ t = text; ++ c = cmd + dynlen; ++ *(c++) = ' '; ++ *(c++) = '\''; ++ while (*t) { ++ // prefix ' with '\' ++ if (*t == '\'') { ++ *(c++) = '\''; ++ *(c++) = '\\'; ++ *(c++) = '\''; ++ } ++ *(c++) = *(t++); ++ } ++ *(c++) = '\''; ++ *(c++) = 0; ++ FILE *stream = popen(cmd, "r"); ++ if (!stream) ++ die("could not popen dynamic command (%s):", cmd); ++ readstdin(stream); ++ int r = pclose(stream); ++ if (r == -1) ++ die("could not pclose dynamic command"); ++ free(cmd); ++} ++ + static void + match(void) + { +@@ -221,6 +263,16 @@ match(void) + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + ++ if (dynamic) { ++ refreshoptions(); ++ matches = matchend = NULL; ++ for (item = items; item && item->text; item++) ++ appenditem(item, &matches, &matchend); ++ curr = sel = matches; ++ calcoffsets(); ++ return; ++ } ++ + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) +@@ -519,14 +571,14 @@ paste(void) + } + + static void +-readstdin(void) ++readstdin(FILE* stream) + { + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + /* read each line from stdin and add it to the item list */ +- for (i = 0; fgets(buf, sizeof buf, stdin); i++) { ++ for (i = 0; fgets(buf, sizeof buf, stream); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); +@@ -544,7 +596,7 @@ readstdin(void) + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; +- lines = MIN(lines, i); ++ lines = MIN(max_lines, i); + } + + static void +@@ -690,7 +742,8 @@ static void + usage(void) + { + fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" +- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); ++ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n" ++ " [-dy command]\n", stderr); + exit(1); + } + +@@ -733,6 +786,8 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; ++ else if (!strcmp(argv[i], "-dy")) /* dynamic command to run */ ++ dynamic = argv[++i] && *argv[i] ? argv[i] : NULL; + else + usage(); + +@@ -757,11 +812,14 @@ main(int argc, char *argv[]) + die("pledge"); + #endif + ++ max_lines = lines; + if (fast && !isatty(0)) { + grabkeyboard(); +- readstdin(); ++ if (!dynamic) ++ readstdin(stdin); + } else { +- readstdin(); ++ if (!dynamic) ++ readstdin(stdin); + grabkeyboard(); + } + setup(); +-- +2.34.1 + diff --git a/dmenu/patches/dmenu-fuzzyhighlight-5.3.diff b/dmenu/patches/dmenu-fuzzyhighlight-5.3.diff new file mode 100644 index 0000000..511bd31 --- /dev/null +++ b/dmenu/patches/dmenu-fuzzyhighlight-5.3.diff @@ -0,0 +1,165 @@ +From dd0327d33a3c5269ed128e0903787365625bd507 Mon Sep 17 00:00:00 2001 +From: Justinas Grigas <dev@jstnas.com> +Date: Sun, 16 Jun 2024 00:38:53 +0100 +Subject: [PATCH] fuzzyhighlight: highlight fuzzy matches + +This patch actually fixes the highlighted character offset +--- + config.def.h | 2 ++ + dmenu.1 | 20 ++++++++++++++++++ + dmenu.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++--- + 3 files changed, 76 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..64eab2a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -11,6 +11,8 @@ static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, ++ [SchemeSelHighlight] = { "#ffc978", "#005577" }, ++ [SchemeNormHighlight] = { "#ffc978", "#222222" }, + [SchemeOut] = { "#000000", "#00ffff" }, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..472b179 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -20,6 +20,14 @@ dmenu \- dynamic menu + .IR color ] + .RB [ \-sf + .IR color ] ++.RB [ \-nhb ++.IR color ] ++.RB [ \-nhf ++.IR color ] ++.RB [ \-shb ++.IR color ] ++.RB [ \-shf ++.IR color ] + .RB [ \-w + .IR windowid ] + .P +@@ -75,6 +83,18 @@ defines the selected background color. + .BI \-sf " color" + defines the selected foreground color. + .TP ++.BI \-nhb " color" ++defines the normal highlight background color. ++.TP ++.BI \-nhf " color" ++defines the normal highlight foreground color. ++.TP ++.BI \-shb " color" ++defines the selected highlight background color. ++.TP ++.BI \-shf " color" ++defines the selected highlight foreground color. ++.TP + .B \-v + prints version information to stdout, then exits. + .TP +diff --git a/dmenu.c b/dmenu.c +index 40f93e0..662131a 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -25,7 +25,9 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight, ++ SchemeOut, SchemeLast }; /* color schemes */ ++ + + struct item { + char *text; +@@ -129,9 +131,47 @@ cistrstr(const char *h, const char *n) + return NULL; + } + ++static void ++drawhighlights(struct item *item, int x, int y, int maxw) ++{ ++ int i, indent; ++ char c, *highlight; ++ ++ if (!(strlen(item->text) && strlen(text))) ++ return; ++ ++ drw_setscheme(drw, scheme[item == sel ++ ? SchemeSelHighlight ++ : SchemeNormHighlight]); ++ for (i = 0, highlight = item->text; *highlight && text[i];) { ++ if (!fstrncmp(highlight, &text[i], 1)) { ++ /* get indentation */ ++ c = *highlight; ++ *highlight = '\0'; ++ indent = TEXTW(item->text); ++ *highlight = c; ++ ++ /* highlight character */ ++ c = highlight[1]; ++ highlight[1] = '\0'; ++ drw_text( ++ drw, ++ x + indent - (lrpad / 2.), ++ y, ++ MIN(maxw - indent, TEXTW(highlight) - lrpad), ++ bh, 0, highlight, 0 ++ ); ++ highlight[1] = c; ++ ++i; ++ } ++ ++highlight; ++ } ++} ++ + static int + drawitem(struct item *item, int x, int y, int w) + { ++ int r; + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) +@@ -139,7 +179,9 @@ drawitem(struct item *item, int x, int y, int w) + else + drw_setscheme(drw, scheme[SchemeNorm]); + +- return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); ++ r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); ++ drawhighlights(item, x, y, w); ++ return r; + } + + static void +@@ -716,7 +758,8 @@ static void + usage(void) + { + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" +- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); ++ " [-nb color] [-nf color] [-sb color] [-sf color]\n" ++ " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]"); + } + + int +@@ -756,6 +799,14 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; ++ 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]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else +-- +2.45.2 + diff --git a/dmenu/patches/dmenu-fuzzymatch-5.3.diff b/dmenu/patches/dmenu-fuzzymatch-5.3.diff new file mode 100644 index 0000000..1df92a5 --- /dev/null +++ b/dmenu/patches/dmenu-fuzzymatch-5.3.diff @@ -0,0 +1,196 @@ +From d166c2c90f777f5a80f628d493cdcf7bf03034b8 Mon Sep 17 00:00:00 2001 +From: Justinas Grigas <dev@jstnas.com> +Date: Mon, 17 Jun 2024 02:52:15 +0100 +Subject: [PATCH] fuzzymatch: Add support for fuzzy-matching + +This version of the patch updates the manpage. +--- + config.def.h | 1 + + config.mk | 2 +- + dmenu.1 | 5 ++- + dmenu.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++- + 4 files changed, 95 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..a4e6174 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,7 @@ + /* Default settings; can be overriden by command line. */ + + static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ ++static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ + /* -fn option overrides fonts[0]; default X11 font or font set */ + static const char *fonts[] = { + "monospace:size=10" +diff --git a/config.mk b/config.mk +index 137f7c8..6a19175 100644 +--- a/config.mk ++++ b/config.mk +@@ -21,7 +21,7 @@ FREETYPEINC = /usr/include/freetype2 + + # includes and libs + INCS = -I$(X11INC) -I$(FREETYPEINC) +-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) ++LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..e2c5ef4 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -3,7 +3,7 @@ + dmenu \- dynamic menu + .SH SYNOPSIS + .B dmenu +-.RB [ \-bfiv ] ++.RB [ \-bFfiv ] + .RB [ \-l + .IR lines ] + .RB [ \-m +@@ -40,6 +40,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. + .B \-b + dmenu appears at the bottom of the screen. + .TP ++.B \-F ++disables fuzzy matching. ++.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. +diff --git a/dmenu.c b/dmenu.c +index 40f93e0..03e1f45 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -1,6 +1,7 @@ + /* See LICENSE file for copyright and license details. */ + #include <ctype.h> + #include <locale.h> ++#include <math.h> + #include <stdio.h> + #include <stdlib.h> + #include <string.h> +@@ -31,6 +32,7 @@ struct item { + char *text; + struct item *left, *right; + int out; ++ double distance; + }; + + static char text[BUFSIZ] = ""; +@@ -226,9 +228,93 @@ grabkeyboard(void) + die("cannot grab keyboard"); + } + ++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(); ++} ++ + static void + match(void) + { ++ if (fuzzy) { ++ fuzzymatch(); ++ return; ++ } + static char **tokv = NULL; + static int tokn = 0; + +@@ -715,7 +801,7 @@ setup(void) + static void + usage(void) + { +- die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ die("usage: dmenu [-bFfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); + } + +@@ -732,6 +818,8 @@ main(int argc, char *argv[]) + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; ++ else if (!strcmp(argv[i], "-F")) /* disables fuzzy matching */ ++ fuzzy = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ +-- +2.45.2 + diff --git a/dmenu/patches/dmenu-lineheight-5.2.diff b/dmenu/patches/dmenu-lineheight-5.2.diff new file mode 100644 index 0000000..a5e8468 --- /dev/null +++ b/dmenu/patches/dmenu-lineheight-5.2.diff @@ -0,0 +1,106 @@ +From ba103e38ea4ab07f9a3ee90627714b9bea17c329 Mon Sep 17 00:00:00 2001 +From: pskry <peter@skrypalle.dk> +Date: Sun, 8 Nov 2020 22:04:22 +0100 +Subject: [PATCH] Add an option which defines the lineheight + +Despite both the panel and dmenu using the same font (a Terminus 12), +dmenu is shorter and the panel is visible from under the dmenu bar. +The appearance can be even more distracting when using similar colors +for background and selections. With the option added by this patch, +dmenu can be launched with a '-h 24', thus completely covering the panel. +--- + config.def.h | 3 +++ + dmenu.1 | 5 +++++ + dmenu.c | 11 ++++++++--- + 3 files changed, 16 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..4394dec 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -15,6 +15,9 @@ static const char *colors[SchemeLast][2] = { + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; ++/* -h option; minimum height of a menu line */ ++static unsigned int lineheight = 0; ++static unsigned int min_lineheight = 8; + + /* + * Characters not considered part of a word while deleting words +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..f2a82b4 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -6,6 +6,8 @@ dmenu \- dynamic menu + .RB [ \-bfiv ] + .RB [ \-l + .IR lines ] ++.RB [ \-h ++.IR height ] + .RB [ \-m + .IR monitor ] + .RB [ \-p +@@ -50,6 +52,9 @@ dmenu matches menu items case insensitively. + .BI \-l " lines" + dmenu lists items vertically, with the given number of lines. + .TP ++.BI \-h " height" ++dmenu uses a menu line of at least 'height' pixels tall, but no less than 8. ++.TP + .BI \-m " monitor" + dmenu is displayed on the monitor number supplied. Monitor numbers are starting + from 0. +diff --git a/dmenu.c b/dmenu.c +index e7be8af..82b204b 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -148,7 +148,7 @@ drawmenu(void) + { + unsigned int curpos; + struct item *item; +- int x = 0, y = 0, w; ++ int x = 0, y = 0, fh = drw->fonts->h, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); +@@ -165,7 +165,7 @@ drawmenu(void) + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); +- drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); ++ drw_rect(drw, x + curpos, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); + } + + if (lines > 0) { +@@ -630,6 +630,7 @@ setup(void) + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; ++ bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + #ifdef XINERAMA +@@ -710,7 +711,7 @@ setup(void) + static void + usage(void) + { +- die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ die("usage: dmenu [-bfiv] [-l lines] [-h height] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); + } + +@@ -737,6 +738,10 @@ main(int argc, char *argv[]) + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); ++ else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ ++ lineheight = atoi(argv[++i]); ++ lineheight = MAX(lineheight, min_lineheight); ++ } + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ +-- +2.38.1 + diff --git a/dmenu/patches/dmenu-mousesupporthoverbgcol-20210123-1a13d04.diff b/dmenu/patches/dmenu-mousesupporthoverbgcol-20210123-1a13d04.diff new file mode 100644 index 0000000..df53931 --- /dev/null +++ b/dmenu/patches/dmenu-mousesupporthoverbgcol-20210123-1a13d04.diff @@ -0,0 +1,196 @@ +From 51331aed8a535323702b73681c5ce2af6f8f5868 Mon Sep 17 00:00:00 2001 +From: Karol Kosek <krkk@krkk.ct8.pl> +Date: Sat, 23 Jan 2021 20:16:19 +0100 +Subject: [PATCH] fix mouse hover on vertical lists with prompt text + +--- + dmenu.c | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 158 insertions(+), 1 deletion(-) + +diff --git a/dmenu.c b/dmenu.c +index 65f25ce..aff2768 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -500,6 +500,156 @@ draw: + drawmenu(); + } + ++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; ++ /* disabled below, needs to be fixed */ ++ /* ++ 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; ++ } ++ } ++} ++ ++static void ++mousemove(XEvent *e) ++{ ++ struct item *item; ++ XPointerMovedEvent *ev = &e->xmotion; ++ int x = 0, y = 0, h = bh, w; ++ ++ if (lines > 0) { ++ w = mw - x; ++ for (item = curr; item != next; item = item->right) { ++ y += h; ++ if (ev->y >= y && ev->y <= (y + h)) { ++ sel = item; ++ calcoffsets(); ++ drawmenu(); ++ return; ++ } ++ } ++ } else if (matches) { ++ x += inputw + promptw; ++ w = TEXTW("<"); ++ 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) { ++ sel = item; ++ calcoffsets(); ++ drawmenu(); ++ return; ++ } ++ } ++ } ++} ++ + static void + paste(void) + { +@@ -561,6 +711,12 @@ run(void) + break; + cleanup(); + exit(1); ++ case ButtonPress: ++ buttonpress(&ev); ++ break; ++ case MotionNotify: ++ mousemove(&ev); ++ break; + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); +@@ -658,7 +814,8 @@ setup(void) + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; +- swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; ++ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ++ ButtonPressMask | PointerMotionMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); +-- +2.30.0 + diff --git a/dmenu/patches/dmenu-navhistory-5.0.diff b/dmenu/patches/dmenu-navhistory-5.0.diff new file mode 100644 index 0000000..7038b19 --- /dev/null +++ b/dmenu/patches/dmenu-navhistory-5.0.diff @@ -0,0 +1,255 @@ +From a4a08baf35edb6b50ed14f76e99d0c6fe790759d Mon Sep 17 00:00:00 2001 +From: Max Schillinger <maxschillinger@web.de> +Date: Fri, 9 Jul 2021 17:17:36 +0200 +Subject: [PATCH] Bug fix: Writing first entry to history file was skipped + +--- + config.def.h | 2 + + dmenu.1 | 5 ++ + dmenu.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++- + dmenu_run | 2 +- + 4 files changed, 151 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..e3e1b53 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -15,6 +15,8 @@ static const char *colors[SchemeLast][2] = { + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; ++static unsigned int maxhist = 64; ++static int histnodup = 1; /* if 0, record repeated histories */ + + /* + * Characters not considered part of a word while deleting words +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..ff496dd 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -22,6 +22,8 @@ dmenu \- dynamic menu + .IR color ] + .RB [ \-w + .IR windowid ] ++.RB [ \-H ++.IR histfile ] + .P + .BR dmenu_run " ..." + .SH DESCRIPTION +@@ -80,6 +82,9 @@ prints version information to stdout, then exits. + .TP + .BI \-w " windowid" + embed into windowid. ++.TP ++.BI \-H " histfile" ++save input in histfile and use it for history navigation. + .SH USAGE + dmenu is completely controlled by the keyboard. Items are selected using the + arrow keys, page up, page down, home, and end. +diff --git a/dmenu.c b/dmenu.c +index 65f25ce..5023257 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -53,6 +53,10 @@ static XIC xic; + static Drw *drw; + static Clr *scheme[SchemeLast]; + ++static char *histfile; ++static char **history; ++static size_t histsz, histpos; ++ + #include "config.h" + + static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +@@ -304,6 +308,129 @@ movewordedge(int dir) + } + } + ++static void ++loadhistory(void) ++{ ++ FILE *fp = NULL; ++ static size_t cap = 0; ++ size_t llen; ++ char *line; ++ ++ if (!histfile) { ++ return; ++ } ++ ++ fp = fopen(histfile, "r"); ++ if (!fp) { ++ return; ++ } ++ ++ for (;;) { ++ line = NULL; ++ llen = 0; ++ if (-1 == getline(&line, &llen, fp)) { ++ if (ferror(fp)) { ++ die("failed to read history"); ++ } ++ free(line); ++ break; ++ } ++ ++ if (cap == histsz) { ++ cap += 64 * sizeof(char*); ++ history = realloc(history, cap); ++ if (!history) { ++ die("failed to realloc memory"); ++ } ++ } ++ strtok(line, "\n"); ++ history[histsz] = line; ++ histsz++; ++ } ++ histpos = histsz; ++ ++ if (fclose(fp)) { ++ die("failed to close file %s", histfile); ++ } ++} ++ ++static void ++navhistory(int dir) ++{ ++ static char def[BUFSIZ]; ++ char *p = NULL; ++ size_t len = 0; ++ ++ if (!history || histpos + 1 == 0) ++ return; ++ ++ if (histsz == histpos) { ++ strncpy(def, text, sizeof(def)); ++ } ++ ++ switch(dir) { ++ case 1: ++ if (histpos < histsz - 1) { ++ p = history[++histpos]; ++ } else if (histpos == histsz - 1) { ++ p = def; ++ histpos++; ++ } ++ break; ++ case -1: ++ if (histpos > 0) { ++ p = history[--histpos]; ++ } ++ break; ++ } ++ if (p == NULL) { ++ return; ++ } ++ ++ len = MIN(strlen(p), BUFSIZ - 1); ++ strncpy(text, p, len); ++ text[len] = '\0'; ++ cursor = len; ++ match(); ++} ++ ++static void ++savehistory(char *input) ++{ ++ unsigned int i; ++ FILE *fp; ++ ++ if (!histfile || ++ 0 == maxhist || ++ 0 == strlen(input)) { ++ goto out; ++ } ++ ++ fp = fopen(histfile, "w"); ++ if (!fp) { ++ die("failed to open %s", histfile); ++ } ++ for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) { ++ if (0 >= fprintf(fp, "%s\n", history[i])) { ++ die("failed to write to %s", histfile); ++ } ++ } ++ if (histsz == 0 || !histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */ ++ if (0 >= fputs(input, fp)) { ++ die("failed to write to %s", histfile); ++ } ++ } ++ if (fclose(fp)) { ++ die("failed to close file %s", histfile); ++ } ++ ++out: ++ for (i = 0; i < histsz; i++) { ++ free(history[i]); ++ } ++ free(history); ++} ++ + static void + keypress(XKeyEvent *ev) + { +@@ -388,6 +515,14 @@ keypress(XKeyEvent *ev) + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; ++ case XK_p: ++ navhistory(-1); ++ buf[0]=0; ++ break; ++ case XK_n: ++ navhistory(1); ++ buf[0]=0; ++ break; + default: + return; + } +@@ -466,6 +601,8 @@ insert: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { ++ savehistory((sel && !(ev->state & ShiftMask)) ++ ? sel->text : text); + cleanup(); + exit(0); + } +@@ -690,7 +827,8 @@ static void + usage(void) + { + fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" +- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); ++ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n" ++ " [-H histfile]", stderr); + exit(1); + } + +@@ -715,6 +853,8 @@ main(int argc, char *argv[]) + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ ++ else if (!strcmp(argv[i], "-H")) ++ histfile = argv[++i]; + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) +@@ -757,6 +897,8 @@ main(int argc, char *argv[]) + die("pledge"); + #endif + ++ loadhistory(); ++ + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); +diff --git a/dmenu_run b/dmenu_run +index 834ede5..59ec622 100755 +--- a/dmenu_run ++++ b/dmenu_run +@@ -1,2 +1,2 @@ + #!/bin/sh +-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & ++dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} & +-- +2.25.1 diff --git a/dmenu/patches/dmenu-nonblockingstdincontrol-4.9.diff b/dmenu/patches/dmenu-nonblockingstdincontrol-4.9.diff new file mode 100644 index 0000000..819ca51 --- /dev/null +++ b/dmenu/patches/dmenu-nonblockingstdincontrol-4.9.diff @@ -0,0 +1,304 @@ +From 1a2f76ad1a0ebf65ae7739cd102b69c02fdcd0ba Mon Sep 17 00:00:00 2001 +From: Miles Alan <m@milesalan.com> +Date: Wed, 30 Sep 2020 08:16:47 -0500 +Subject: [PATCH] nonblockingstdincontrol: Add Non-blocking stdin and control + character reloading + +Read from stdin continously in parallel to X events. And also adds support +for using control characters for reloading options (e.g. emptying list +and setting curr/sel). + +The following control characters are supported when sent at the beginning of +a line: +\f - Clear the current items prior to following line +\a - Set the following line to be equal to sel +\b - Set the following line to be equal to curr +--- + dmenu.1 | 6 +-- + dmenu.c | 144 ++++++++++++++++++++++++++++++++++++++------------------ + 2 files changed, 98 insertions(+), 52 deletions(-) + +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..a07532d 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -3,7 +3,7 @@ + dmenu \- dynamic menu + .SH SYNOPSIS + .B dmenu +-.RB [ \-bfiv ] ++.RB [ \-biv ] + .RB [ \-l + .IR lines ] + .RB [ \-m +@@ -40,10 +40,6 @@ which lists programs in the user's $PATH and runs the result in their $SHELL. + .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 +diff --git a/dmenu.c b/dmenu.c +index 6b8f51b..cc87b04 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -1,5 +1,6 @@ + /* See LICENSE file for copyright and license details. */ + #include <ctype.h> ++#include <fcntl.h> + #include <locale.h> + #include <stdio.h> + #include <stdlib.h> +@@ -8,6 +9,8 @@ + #include <time.h> + #include <unistd.h> + ++ ++#include <sys/select.h> + #include <X11/Xlib.h> + #include <X11/Xatom.h> + #include <X11/Xutil.h> +@@ -31,6 +34,7 @@ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + struct item { + char *text; + struct item *left, *right; ++ struct item *next; + int out; + }; + +@@ -41,6 +45,7 @@ static int inputw = 0, promptw; + static int lrpad; /* sum of left and right padding */ + static size_t cursor; + static struct item *items = NULL; ++static struct item *itemstail= NULL; + static struct item *matches, *matchend; + static struct item *prev, *curr, *next, *sel; + static int mon = -1, screen; +@@ -173,6 +178,7 @@ drawmenu(void) + } + } + drw_map(drw, win, 0, 0, mw, mh); ++ XFlush(dpy); + } + + static void +@@ -220,6 +226,7 @@ match(void) + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; ++ int preserve = 0; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ +@@ -230,19 +237,23 @@ match(void) + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; +- for (item = items; item && item->text; item++) { ++ for (item = items; item; item = item->next) { + 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)) ++ if (!tokc || !fstrncmp(text, item->text, textsize)) { + appenditem(item, &matches, &matchend); +- else if (!fstrncmp(tokv[0], item->text, len)) ++ if (sel == item) preserve = 1; ++ } else if (!fstrncmp(tokv[0], item->text, len)) { + appenditem(item, &lprefix, &prefixend); +- else ++ if (sel == item) preserve = 1; ++ } else { + appenditem(item, &lsubstr, &substrend); ++ if (sel == item) preserve = 1; ++ } + } + if (lprefix) { + if (matches) { +@@ -260,7 +271,9 @@ match(void) + matches = lsubstr; + matchend = substrend; + } +- curr = sel = matches; ++ if (!preserve) ++ curr = sel = matches; ++ + calcoffsets(); + } + +@@ -519,40 +532,11 @@ paste(void) + } + + static void +-readstdin(void) +-{ +- char buf[sizeof text], *p; +- size_t i, imax = 0, size = 0; +- unsigned int tmpmax = 0; +- +- /* read each line from stdin and add it to the item list */ +- for (i = 0; fgets(buf, sizeof buf, stdin); i++) { +- if (i + 1 >= size / sizeof *items) +- if (!(items = realloc(items, (size += BUFSIZ)))) +- die("cannot realloc %u bytes:", size); +- if ((p = strchr(buf, '\n'))) +- *p = '\0'; +- if (!(items[i].text = strdup(buf))) +- die("cannot strdup %u bytes:", strlen(buf) + 1); +- items[i].out = 0; +- 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); +-} +- +-static void +-run(void) ++readevent(void) + { + XEvent ev; + +- while (!XNextEvent(dpy, &ev)) { ++ while (XPending(dpy) && !XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, None)) + continue; + switch(ev.type) { +@@ -580,6 +564,80 @@ run(void) + } + } + ++static void ++readstdin(void) ++{ ++ size_t max = 0; ++ char buf[sizeof text], *p, *maxstr; ++ struct item *item; ++ int ctrloffset = 0; ++ ++ ++ /* 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'; ++ ++ ctrloffset = 0; ++ while (ctrloffset + 1 < sizeof buf && ( ++ buf[ctrloffset] == '\a' || ++ buf[ctrloffset] == '\b' || ++ buf[ctrloffset] == '\f' ++ )) { ++ if (buf[ctrloffset] == '\a') ++ sel = item; ++ if (buf[ctrloffset] == '\b') ++ curr = item; ++ if (buf[ctrloffset] == '\f') ++ itemstail = sel = curr = items = NULL; ++ ctrloffset++; ++ } ++ ++ if (!(item->text = strdup(buf+ctrloffset))) ++ die("cannot strdup %u bytes:", strlen(buf+ctrloffset)+1); ++ if (strlen(item->text) > max) { ++ max = strlen(maxstr = item->text); ++ inputw = maxstr ? TEXTW(maxstr) : 0; ++ } ++ item->out = 0; ++ item->next = NULL; ++ ++ if (items == NULL) ++ items = item; ++ if (itemstail) ++ itemstail->next = item; ++ itemstail = item; ++ } ++ 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(); ++ } ++} ++ + static void + setup(void) + { +@@ -682,7 +740,7 @@ setup(void) + static void + usage(void) + { +- fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ fputs("usage: dmenu [-biv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); + } +@@ -691,7 +749,7 @@ int + main(int argc, char *argv[]) + { + XWindowAttributes wa; +- int i, fast = 0; ++ int i; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ +@@ -700,8 +758,6 @@ main(int argc, char *argv[]) + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; +- else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ +- fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; +@@ -752,13 +808,7 @@ main(int argc, char *argv[]) + die("pledge"); + #endif + +- if (fast && !isatty(0)) { +- grabkeyboard(); +- readstdin(); +- } else { +- readstdin(); +- grabkeyboard(); +- } ++ grabkeyboard(); + setup(); + run(); + +-- +2.25.4 + diff --git a/dmenu/patches/dmenu-password-5.0.diff b/dmenu/patches/dmenu-password-5.0.diff new file mode 100644 index 0000000..7a187b9 --- /dev/null +++ b/dmenu/patches/dmenu-password-5.0.diff @@ -0,0 +1,103 @@ +From c4de1032bd4c247bc20b6ab92a10a8d778966679 Mon Sep 17 00:00:00 2001 +From: Mehrad Mahmoudian <m.mahmoudian@gmail.com> +Date: Tue, 4 May 2021 12:05:09 +0300 +Subject: [PATCH] patched with password patch + +--- + dmenu.1 | 5 ++++- + dmenu.c | 21 +++++++++++++++++---- + 2 files changed, 21 insertions(+), 5 deletions(-) + +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..762f707 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -3,7 +3,7 @@ + dmenu \- dynamic menu + .SH SYNOPSIS + .B dmenu +-.RB [ \-bfiv ] ++.RB [ \-bfivP ] + .RB [ \-l + .IR lines ] + .RB [ \-m +@@ -47,6 +47,9 @@ is faster, but will lock up X until stdin reaches end\-of\-file. + .B \-i + dmenu matches menu items case insensitively. + .TP ++.B \-P ++dmenu will not directly display the keyboard input, but instead replace it with dots. All data from stdin will be ignored. ++.TP + .BI \-l " lines" + dmenu lists items vertically, with the given number of lines. + .TP +diff --git a/dmenu.c b/dmenu.c +index 65f25ce..ad8f63b 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -37,7 +37,7 @@ struct item { + static char text[BUFSIZ] = ""; + static char *embed; + static int bh, mw, mh; +-static int inputw = 0, promptw; ++static int inputw = 0, promptw, passwd = 0; + static int lrpad; /* sum of left and right padding */ + static size_t cursor; + static struct item *items = NULL; +@@ -132,6 +132,7 @@ drawmenu(void) + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; ++ char *censort; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); +@@ -143,7 +144,12 @@ drawmenu(void) + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); +- drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); ++ 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); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { +@@ -524,6 +530,11 @@ readstdin(void) + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; ++ if(passwd){ ++ inputw = lines = 0; ++ return; ++ } ++ + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { +@@ -689,7 +700,7 @@ setup(void) + static void + usage(void) + { +- fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ fputs("usage: dmenu [-bfivP] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); + } +@@ -712,7 +723,9 @@ main(int argc, char *argv[]) + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; +- } else if (i + 1 == argc) ++ } else if (!strcmp(argv[i], "-P")) /* is the input a password */ ++ passwd = 1; ++ else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ +-- +2.31.1 + diff --git a/dmenu/patches/dmenu-png-images-5.3.diff b/dmenu/patches/dmenu-png-images-5.3.diff new file mode 100644 index 0000000..5ec78b9 --- /dev/null +++ b/dmenu/patches/dmenu-png-images-5.3.diff @@ -0,0 +1,448 @@ +From 743d86e56e0c1eb4255a08fe338db03752cc99e7 Mon Sep 17 00:00:00 2001 +From: Max Schillinger <maxschillinger@web.de> +Date: Fri, 1 Nov 2024 08:58:49 +0100 +Subject: [PATCH] Support PNG images using libspng + +--- + config.mk | 2 +- + dmenu.c | 62 +++++++++++++++--- + drw.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + drw.h | 5 ++ + util.c | 6 ++ + util.h | 1 + + 6 files changed, 254 insertions(+), 11 deletions(-) + +diff --git a/config.mk b/config.mk +index 137f7c8..3217090 100644 +--- a/config.mk ++++ b/config.mk +@@ -21,7 +21,7 @@ FREETYPEINC = /usr/include/freetype2 + + # includes and libs + INCS = -I$(X11INC) -I$(FREETYPEINC) +-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) ++LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lspng + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +diff --git a/dmenu.c b/dmenu.c +index 804da64..b0e4109 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -38,11 +38,14 @@ static char *embed; + static int bh, mw, mh; + static int inputw = 0, promptw; + static int lrpad; /* sum of left and right padding */ ++static int tbpad; /* sum of top and bottom padding for images */ + static size_t cursor; + static struct item *items = NULL; + static struct item *matches, *matchend; + static struct item *prev, *curr, *next, *sel; + static int mon = -1, screen; ++static char *image_prefix = "PNG_IMAGE:"; ++static int image_size = -1; /* in pixels */ + + static Atom clip, utf8; + static Display *dpy; +@@ -58,12 +61,26 @@ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; + static char *(*fstrstr)(const char *, const char *) = strstr; + + static unsigned int +-textw_clamp(const char *str, unsigned int n) ++textw_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh) + { +- unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; ++ unsigned int w; ++ if (startswith(image_prefix, str) && ++ (w = drw_getimagewidth_clamp(drw, str + strlen(image_prefix), maxw, maxh))) ++ return MIN(w + lrpad, n); ++ w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); + } + ++static unsigned int ++texth_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh) ++{ ++ unsigned int h; ++ if (startswith(image_prefix, str) && ++ (h = drw_getimageheight_clamp(drw, str + strlen(image_prefix), maxw, maxh))) ++ return MIN(h + tbpad, n); ++ return MIN(bh, n); ++} ++ + static void + appenditem(struct item *item, struct item **list, struct item **last) + { +@@ -83,15 +100,19 @@ calcoffsets(void) + int i, n; + + if (lines > 0) +- n = lines * bh; ++ n = mh - 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 : textw_clamp(next->text, n)) > n) ++ if ((i += (lines > 0) ++ ? texth_clamp(next->text, n, mw - lrpad, image_size) ++ : textw_clamp(next->text, n, image_size, bh)) > 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) ++ if ((i += (lines > 0) ++ ? texth_clamp(prev->left->text, n, mw - lrpad, image_size) ++ : textw_clamp(prev->left->text, n, image_size, bh)) > n) + break; + } + +@@ -139,7 +160,18 @@ drawitem(struct item *item, int x, int y, int w) + else + drw_setscheme(drw, scheme[SchemeNorm]); + +- return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); ++ int vertical = lines > 0; ++ if (startswith(image_prefix, item->text)) { ++ char *path = item->text + strlen(image_prefix); ++ unsigned int image_width = vertical ? w - lrpad : image_size; ++ unsigned int image_height = vertical ? image_size : bh; ++ drw_image(drw, &x, &y, &image_width, &image_height, ++ lrpad, vertical ? tbpad : 0, path, vertical); ++ if (image_width && image_height) ++ return vertical ? y : x; ++ } ++ int nextx = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); ++ return vertical ? y + bh : nextx; + } + + static void +@@ -169,8 +201,9 @@ drawmenu(void) + + if (lines > 0) { + /* draw vertical list */ ++ y = bh; + for (item = curr; item != next; item = item->right) +- drawitem(item, x, y += bh, mw - x); ++ y = drawitem(item, x, y, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; +@@ -181,7 +214,7 @@ drawmenu(void) + } + x += w; + for (item = curr; item != next; item = item->right) +- x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); ++ x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"), image_size, bh)); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); +@@ -635,7 +668,10 @@ setup(void) + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); +- mh = (lines + 1) * bh; ++ /* default values for image_size */ ++ if (image_size < 0) ++ image_size = (lines > 0) ? 2 * bh : 8 * bh; ++ mh = bh + ((lines > 0) ? MAX(lines * bh, image_size) : 0); + #ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { +@@ -715,7 +751,8 @@ static void + usage(void) + { + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" +- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); ++ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n" ++ " [-ip image_prefix] [-is image_size]"); + } + + int +@@ -757,6 +794,10 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; ++ else if (!strcmp(argv[i], "-ip")) /* image prefix */ ++ image_prefix = argv[++i]; ++ else if (!strcmp(argv[i], "-is")) /* max. image preview size (height or width) */ ++ image_size = atoi(argv[++i]); + else + usage(); + +@@ -775,6 +816,7 @@ main(int argc, char *argv[]) + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; ++ tbpad = lrpad / 2; + + #ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) +diff --git a/drw.c b/drw.c +index c41e6af..20c2125 100644 +--- a/drw.c ++++ b/drw.c +@@ -4,12 +4,24 @@ + #include <string.h> + #include <X11/Xlib.h> + #include <X11/Xft/Xft.h> ++#include <spng.h> + + #include "drw.h" + #include "util.h" + + #define UTF_INVALID 0xFFFD + ++struct image_item { ++ const char *path; ++ int width; ++ int height; ++ char *buf; ++ Pixmap pixmap; ++ struct image_item *next; ++}; ++ ++static struct image_item *images = NULL; ++ + static int + utf8decode(const char *s_in, long *u, int *err) + { +@@ -382,6 +394,163 @@ no_match: + return x + (render ? w : 0); + } + ++static struct image_item * ++load_image(Drw *drw, unsigned int maxw, unsigned int maxh, const char *path) ++{ ++ FILE *png; ++ spng_ctx *ctx = NULL; ++ int ret = 0; ++ struct spng_ihdr ihdr; ++ struct spng_plte plte = {0}; ++ struct spng_row_info row_info = {0}; ++ char *spng_buf; ++ int fmt = SPNG_FMT_RGBA8; ++ int crop_width; ++ int crop_height; ++ ++ struct image_item *image = ecalloc(1, sizeof(struct image_item)); ++ image->path = path; ++ image->next = images; ++ images = image; ++ ++ png = fopen(path, "rb"); ++ if (png == NULL) { ++ fprintf(stderr, "error opening input file %s\n", path); ++ return NULL; ++ } ++ ++ /* Create a context */ ++ ctx = spng_ctx_new(0); ++ if (ctx == NULL) { ++ fprintf(stderr, "%s: spng_ctx_new() failed\n", path); ++ return NULL; ++ } ++ ++ /* Ignore and don't calculate chunk CRC's */ ++ spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); ++ ++ /* Set memory usage limits for storing standard and unknown chunks, ++ this is important when reading untrusted files! */ ++ size_t limit = 1024 * 1024 * 64; ++ spng_set_chunk_limits(ctx, limit, limit); ++ ++ spng_set_png_file(ctx, png); ++ ++ ret = spng_get_ihdr(ctx, &ihdr); ++ if (ret) { ++ fprintf(stderr, "%s: spng_get_ihdr() error: %s\n", path, spng_strerror(ret)); ++ return NULL; ++ } ++ ++ ret = spng_get_plte(ctx, &plte); ++ if (ret && ret != SPNG_ECHUNKAVAIL) { ++ fprintf(stderr, "%s: spng_get_plte() error: %s\n", path, spng_strerror(ret)); ++ return NULL; ++ } ++ ++ size_t image_size, bytes_per_row; /* size in bytes, not in pixels */ ++ ++ ret = spng_decoded_image_size(ctx, fmt, &image_size); ++ if (ret) ++ return NULL; ++ ++ spng_buf = malloc(image_size); ++ if (!spng_buf) ++ return NULL; ++ ++ ret = spng_decode_image(ctx, NULL, 0, fmt, SPNG_DECODE_PROGRESSIVE); ++ if (ret) { ++ fprintf(stderr, "%s: progressive spng_decode_image() error: %s\n", ++ path, spng_strerror(ret)); ++ return NULL; ++ } ++ ++ /* ihdr.height will always be non-zero if spng_get_ihdr() succeeds */ ++ bytes_per_row = image_size / ihdr.height; ++ crop_width = MIN(ihdr.width, maxw); ++ crop_height = MIN(ihdr.height, maxh); ++ ++ do { ++ ret = spng_get_row_info(ctx, &row_info); ++ if (ret) ++ break; ++ ret = spng_decode_row(ctx, spng_buf + row_info.row_num * bytes_per_row, bytes_per_row); ++ } while (!ret && row_info.row_num < crop_height); ++ ++ if (ret != SPNG_EOI && row_info.row_num < crop_height) ++ fprintf(stderr, "%s: progressive decode error: %s\n", path, spng_strerror(ret)); ++ ++ image->buf = calloc(ihdr.width * crop_height * 4, sizeof(char)); ++ for (int i = 0; i < ihdr.width * crop_height; i++) { ++ /* RGBA to BGRA */ ++ image->buf[i*4+2] = spng_buf[i*4+0]; ++ image->buf[i*4+1] = spng_buf[i*4+1]; ++ image->buf[i*4+0] = spng_buf[i*4+2]; ++ image->buf[i*4+3] = spng_buf[i*4+3]; ++ } ++ image->width = crop_width; ++ image->height = crop_height; ++ ++ XImage *img = XCreateImage(drw->dpy, CopyFromParent, DefaultDepth(drw->dpy, drw->screen), ++ ZPixmap, 0, image->buf, ihdr.width, crop_height, 32, 0); ++ image->pixmap = XCreatePixmap(drw->dpy, drw->root, crop_width, crop_height, 24); ++ XPutImage(drw->dpy, image->pixmap, drw->gc, img, 0, 0, 0, 0, crop_width, crop_height); ++ spng_ctx_free(ctx); ++ fclose(png); ++ return image; ++} ++ ++void ++drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h, ++ unsigned int lrpad, unsigned int tbpad, const char *path, int vertical) ++{ ++ /* *x and *y refer to box position including padding, ++ * *w and *h are the maximum image width and height without padding */ ++ struct image_item *image = NULL; ++ int render = *x || *y; ++ int crop_width, crop_height; ++ ++ // find path in images ++ for (struct image_item *item = images; item != NULL; item = item->next) { ++ if (!strcmp(item->path, path)) { ++ image = item; ++ if (!image->buf) ++ goto file_error; ++ break; ++ } ++ } ++ ++ if (!image && !(image = load_image(drw, *w, *h, path))) ++ goto file_error; ++ ++ if (!render) { ++ *w = image->width; ++ *h = image->height; ++ return; ++ } ++ ++ crop_width = MIN(image->width, *w); ++ crop_height = MIN(image->height, *h); ++ if (vertical) ++ *h = crop_height; ++ else ++ *w = crop_width; ++ ++ XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel); ++ XFillRectangle(drw->dpy, drw->drawable, drw->gc, *x, *y, *w + lrpad, *h + tbpad); ++ XCopyArea(drw->dpy, image->pixmap, drw->drawable, drw->gc, 0, 0, ++ crop_width, crop_height, *x + lrpad/2, *y + tbpad/2); ++ ++ if (vertical) ++ *y += *h + tbpad; ++ else ++ *x += *w + lrpad; ++ return; ++ ++file_error: ++ *w = *h = 0; ++} ++ + void + drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) + { +@@ -424,6 +593,26 @@ drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, + *h = font->h; + } + ++unsigned int ++drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh) ++{ ++ int x = 0, y = 0; ++ unsigned int w = maxw, h = maxh; ++ if (drw && path && maxw && maxh) ++ drw_image(drw, &x, &y, &w, &h, 0, 0, path, 0); ++ return MIN(maxw, w); ++} ++ ++unsigned int ++drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh) ++{ ++ int x = 0, y = 0; ++ unsigned int w = maxw, h = maxh; ++ if (drw && path && maxw && maxh) ++ drw_image(drw, &x, &y, &w, &h, 0, 0, path, 1); ++ return MIN(maxh, h); ++} ++ + Cur * + drw_cur_create(Drw *drw, int shape) + { +diff --git a/drw.h b/drw.h +index fd7631b..330722d 100644 +--- a/drw.h ++++ b/drw.h +@@ -38,6 +38,10 @@ unsigned int drw_fontset_getwidth(Drw *drw, const char *text); + unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); + void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + ++/* Image abstraction */ ++unsigned int drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh); ++unsigned int drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh); ++ + /* 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); +@@ -53,6 +57,7 @@ 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); ++void drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h, unsigned int lrpad, unsigned int tbpad, const char *path, int vertical); + + /* Map functions */ + void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); +diff --git a/util.c b/util.c +index 8e26a51..975735b 100644 +--- a/util.c ++++ b/util.c +@@ -35,3 +35,9 @@ ecalloc(size_t nmemb, size_t size) + die("calloc:"); + return p; + } ++ ++int ++startswith(const char* prefix, const char* str) ++{ ++ return strncmp(prefix, str, strlen(prefix)) == 0; ++} +diff --git a/util.h b/util.h +index c0a50d4..6db39c8 100644 +--- a/util.h ++++ b/util.h +@@ -7,3 +7,4 @@ + + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); ++int startswith(const char* prefix, const char* str); +-- +2.47.0 + diff --git a/dmenu/patches/dmenu-vi_mode-20230416-0fe460d.diff b/dmenu/patches/dmenu-vi_mode-20230416-0fe460d.diff new file mode 100644 index 0000000..514b684 --- /dev/null +++ b/dmenu/patches/dmenu-vi_mode-20230416-0fe460d.diff @@ -0,0 +1,300 @@ +diff --git a/config.def.h b/config.def.h +index 1edb647..7bf5f4a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,6 +12,7 @@ static const char *colors[SchemeLast][2] = { + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, ++ [SchemeCursor] = { "#222222", "#bbbbbb"}, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; +@@ -21,3 +22,15 @@ static unsigned int lines = 0; + * for example: " /?\"&[]" + */ + static const char worddelimiters[] = " "; ++ ++/* ++ * -vi option; if nonzero, vi mode is always enabled and can be ++ * accessed with the global_esc keysym + mod mask ++ */ ++static unsigned int vi_mode = 1; ++static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ ++static Key global_esc = { XK_n, Mod1Mask }; /* escape key when vi mode is not enabled explicitly */ ++static Key quit_keys[] = { ++ /* keysym modifier */ ++ { XK_q, 0 } ++}; +diff --git a/dmenu.c b/dmenu.c +index 62f1089..8066271 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -26,7 +26,7 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeOut, SchemeCursor, SchemeLast }; /* color schemes */ + + struct item { + char *text; +@@ -34,6 +34,11 @@ struct item { + int out; + }; + ++typedef struct { ++ KeySym ksym; ++ unsigned int state; ++} Key; ++ + static char text[BUFSIZ] = ""; + static char *embed; + static int bh, mw, mh; +@@ -44,6 +49,7 @@ static struct item *items = NULL; + static struct item *matches, *matchend; + static struct item *prev, *curr, *next, *sel; + static int mon = -1, screen; ++static unsigned int using_vi_mode = 0; + + static Atom clip, utf8; + static Display *dpy; +@@ -163,7 +169,15 @@ drawmenu(void) + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); +- if ((curpos += lrpad / 2 - 1) < w) { ++ curpos += lrpad / 2 - 1; ++ if (using_vi_mode && text[0] != '\0') { ++ drw_setscheme(drw, scheme[SchemeCursor]); ++ char vi_char[] = {text[cursor], '\0'}; ++ drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0); ++ } else if (using_vi_mode) { ++ drw_setscheme(drw, scheme[SchemeNorm]); ++ drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); ++ } else if (curpos < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } +@@ -321,6 +335,181 @@ movewordedge(int dir) + } + } + ++static void ++vi_keypress(KeySym ksym, const XKeyEvent *ev) ++{ ++ static const size_t quit_len = LENGTH(quit_keys); ++ if (ev->state & ControlMask) { ++ switch(ksym) { ++ /* movement */ ++ case XK_d: /* fallthrough */ ++ if (next) { ++ sel = curr = next; ++ calcoffsets(); ++ goto draw; ++ } else ++ ksym = XK_G; ++ break; ++ case XK_u: ++ if (prev) { ++ sel = curr = prev; ++ calcoffsets(); ++ goto draw; ++ } else ++ ksym = XK_g; ++ break; ++ case XK_p: /* fallthrough */ ++ case XK_P: break; ++ case XK_c: ++ cleanup(); ++ exit(1); ++ case XK_Return: /* fallthrough */ ++ case XK_KP_Enter: break; ++ default: return; ++ } ++ } ++ ++ switch(ksym) { ++ /* movement */ ++ case XK_0: ++ cursor = 0; ++ break; ++ case XK_dollar: ++ if (text[cursor + 1] != '\0') { ++ cursor = strlen(text) - 1; ++ break; ++ } ++ break; ++ case XK_b: ++ movewordedge(-1); ++ break; ++ case XK_e: ++ cursor = nextrune(+1); ++ movewordedge(+1); ++ if (text[cursor] == '\0') ++ --cursor; ++ else ++ cursor = nextrune(-1); ++ break; ++ case XK_g: ++ if (sel == matches) { ++ break; ++ } ++ sel = curr = matches; ++ calcoffsets(); ++ break; ++ case XK_G: ++ 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_h: ++ if (cursor) ++ cursor = nextrune(-1); ++ break; ++ case XK_j: ++ if (sel && sel->right && (sel = sel->right) == next) { ++ curr = next; ++ calcoffsets(); ++ } ++ break; ++ case XK_k: ++ if (sel && sel->left && (sel = sel->left)->right == curr) { ++ curr = prev; ++ calcoffsets(); ++ } ++ break; ++ case XK_l: ++ if (text[cursor] != '\0' && text[cursor + 1] != '\0') ++ cursor = nextrune(+1); ++ else if (text[cursor] == '\0' && cursor) ++ --cursor; ++ break; ++ case XK_w: ++ movewordedge(+1); ++ if (text[cursor] != '\0' && text[cursor + 1] != '\0') ++ cursor = nextrune(+1); ++ else if (cursor) ++ --cursor; ++ break; ++ /* insertion */ ++ case XK_a: ++ cursor = nextrune(+1); ++ /* fallthrough */ ++ case XK_i: ++ using_vi_mode = 0; ++ break; ++ case XK_A: ++ if (text[cursor] != '\0') ++ cursor = strlen(text); ++ using_vi_mode = 0; ++ break; ++ case XK_I: ++ cursor = using_vi_mode = 0; ++ break; ++ case XK_p: ++ if (text[cursor] != '\0') ++ cursor = nextrune(+1); ++ XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, ++ utf8, utf8, win, CurrentTime); ++ return; ++ case XK_P: ++ XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, ++ utf8, utf8, win, CurrentTime); ++ return; ++ /* deletion */ ++ case XK_D: ++ text[cursor] = '\0'; ++ if (cursor) ++ cursor = nextrune(-1); ++ match(); ++ break; ++ case XK_x: ++ cursor = nextrune(+1); ++ insert(NULL, nextrune(-1) - cursor); ++ if (text[cursor] == '\0' && text[0] != '\0') ++ --cursor; ++ match(); ++ break; ++ /* misc. */ ++ case XK_Return: ++ case XK_KP_Enter: ++ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); ++ if (!(ev->state & ControlMask)) { ++ cleanup(); ++ exit(0); ++ } ++ if (sel) ++ sel->out = 1; ++ break; ++ case XK_Tab: ++ if (!sel) ++ return; ++ strncpy(text, sel->text, sizeof text - 1); ++ text[sizeof text - 1] = '\0'; ++ cursor = strlen(text) - 1; ++ match(); ++ break; ++ default: ++ for (size_t i = 0; i < quit_len; ++i) ++ if (quit_keys[i].ksym == ksym && ++ (quit_keys[i].state & ev->state) == quit_keys[i].state) { ++ cleanup(); ++ exit(1); ++ } ++ } ++ ++draw: ++ drawmenu(); ++} ++ + static void + keypress(XKeyEvent *ev) + { +@@ -340,6 +529,18 @@ keypress(XKeyEvent *ev) + break; + } + ++ if (using_vi_mode) { ++ vi_keypress(ksym, ev); ++ return; ++ } else if (vi_mode && ++ (ksym == global_esc.ksym && ++ (ev->state & global_esc.state) == global_esc.state)) { ++ using_vi_mode = 1; ++ if (cursor) ++ cursor = nextrune(-1); ++ goto draw; ++ } ++ + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; +@@ -543,6 +744,8 @@ paste(void) + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } ++ if (using_vi_mode && text[cursor] == '\0') ++ --cursor; + drawmenu(); + } + +@@ -738,6 +941,11 @@ main(int argc, char *argv[]) + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; ++ } else if (!strcmp(argv[i], "-vi")) { ++ vi_mode = 1; ++ using_vi_mode = start_mode; ++ global_esc.ksym = XK_Escape; ++ global_esc.state = 0; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ diff --git a/dmenu/patches/dmenu-xresources-alt-5.0.diff b/dmenu/patches/dmenu-xresources-alt-5.0.diff new file mode 100644 index 0000000..f7f052f --- /dev/null +++ b/dmenu/patches/dmenu-xresources-alt-5.0.diff @@ -0,0 +1,182 @@ +diff -rupN orig/config.def.h patched/config.def.h +--- orig/config.def.h 2021-04-16 06:30:47.713924755 +0300 ++++ patched/config.def.h 2021-04-16 06:34:14.956933252 +0300 +@@ -2,16 +2,25 @@ + /* Default settings; can be overriden by command line. */ + + static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ ++ + /* -fn option overrides fonts[0]; default X11 font or font set */ ++static char font[] = "monospace:size=10"; + static const char *fonts[] = { +- "monospace:size=10" ++ font, ++ "monospace:size=10", + }; +-static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +-static const char *colors[SchemeLast][2] = { ++ ++static char *prompt = NULL; /* -p option; prompt to the left of input field */ ++ ++static char normfgcolor[] = "#bbbbbb"; ++static char normbgcolor[] = "#222222"; ++static char selfgcolor[] = "#eeeeee"; ++static char selbgcolor[] = "#005577"; ++static char *colors[SchemeLast][2] = { + /* fg bg */ +- [SchemeNorm] = { "#bbbbbb", "#222222" }, +- [SchemeSel] = { "#eeeeee", "#005577" }, +- [SchemeOut] = { "#000000", "#00ffff" }, ++ [SchemeNorm] = { normfgcolor, normbgcolor }, ++ [SchemeSel] = { selfgcolor, selbgcolor }, ++ [SchemeOut] = { "#000000", "#00ffff" }, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; +@@ -21,3 +30,15 @@ static unsigned int lines = 0; + * for example: " /?\"&[]" + */ + static const char worddelimiters[] = " "; ++ ++/* ++ * Xresources preferences to load at startup ++ */ ++ResourcePref resources[] = { ++ { "font", STRING, &font }, ++ { "normfgcolor", STRING, &normfgcolor }, ++ { "normbgcolor", STRING, &normbgcolor }, ++ { "selfgcolor", STRING, &selfgcolor }, ++ { "selbgcolor", STRING, &selbgcolor }, ++ { "prompt", STRING, &prompt }, ++}; +diff -rupN orig/dmenu.c patched/dmenu.c +--- orig/dmenu.c 2021-04-16 06:30:47.715924755 +0300 ++++ patched/dmenu.c 2021-04-16 06:30:59.668925245 +0300 +@@ -11,6 +11,7 @@ + #include <X11/Xlib.h> + #include <X11/Xatom.h> + #include <X11/Xutil.h> ++#include <X11/Xresource.h> + #ifdef XINERAMA + #include <X11/extensions/Xinerama.h> + #endif +@@ -53,6 +54,21 @@ static XIC xic; + static Drw *drw; + static Clr *scheme[SchemeLast]; + ++/* Xresources preferences */ ++enum resource_type { ++ STRING = 0, ++ INTEGER = 1, ++ FLOAT = 2 ++}; ++typedef struct { ++ char *name; ++ enum resource_type type; ++ void *dst; ++} ResourcePref; ++ ++static void load_xresources(void); ++static void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst); ++ + #include "config.h" + + static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +@@ -395,7 +411,7 @@ keypress(XKeyEvent *ev) + + switch(ksym) { + default: +-insert: ++ insert: + if (!iscntrl(*buf)) + insert(buf, len); + break; +@@ -547,6 +563,54 @@ readstdin(void) + lines = MIN(lines, i); + } + ++void ++resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) ++{ ++ char *sdst = NULL; ++ int *idst = NULL; ++ float *fdst = NULL; ++ sdst = dst; ++ idst = dst; ++ fdst = dst; ++ char fullname[256]; ++ char *type; ++ XrmValue ret; ++ snprintf(fullname, sizeof(fullname), "%s.%s", "dmenu", name); ++ fullname[sizeof(fullname) - 1] = '\0'; ++ XrmGetResource(db, fullname, "*", &type, &ret); ++ if (!(ret.addr == NULL || strncmp("String", type, 64))) ++ { ++ switch (rtype) { ++ case STRING: ++ strcpy(sdst, ret.addr); ++ break; ++ case INTEGER: ++ *idst = strtoul(ret.addr, NULL, 10); ++ break; ++ case FLOAT: ++ *fdst = strtof(ret.addr, NULL); ++ break; ++ } ++ } ++} ++ ++void ++load_xresources(void) ++{ ++ Display *display; ++ char *resm; ++ XrmDatabase db; ++ ResourcePref *p; ++ display = XOpenDisplay(NULL); ++ resm = XResourceManagerString(display); ++ if (!resm) ++ return; ++ db = XrmGetStringDatabase(resm); ++ for (p = resources; p < resources + LENGTH(resources); p++) ++ resource_load(db, p->name, p->type, p->dst); ++ XCloseDisplay(display); ++} ++ + static void + run(void) + { +@@ -700,6 +764,9 @@ main(int argc, char *argv[]) + XWindowAttributes wa; + int i, fast = 0; + ++ XrmInitialize(); ++ load_xresources(); ++ + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ +diff -rupN orig/drw.c patched/drw.c +--- orig/drw.c 2021-04-16 06:30:47.718924755 +0300 ++++ patched/drw.c 2021-04-16 06:30:59.670925245 +0300 +@@ -208,7 +208,7 @@ drw_clr_create(Drw *drw, Clr *dest, cons + /* 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) ++drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount) + { + size_t i; + Clr *ret; +diff -rupN orig/drw.h patched/drw.h +--- orig/drw.h 2021-04-16 06:30:47.718924755 +0300 ++++ patched/drw.h 2021-04-16 06:30:59.671925245 +0300 +@@ -39,7 +39,7 @@ void drw_font_getexts(Fnt *font, const c + + /* 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); ++Clr *drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount); + + /* Cursor abstraction */ + Cur *drw_cur_create(Drw *drw, int shape); diff --git a/dmenu/patches/dmenu-xyw-5.2.diff b/dmenu/patches/dmenu-xyw-5.2.diff new file mode 100644 index 0000000..312fb26 --- /dev/null +++ b/dmenu/patches/dmenu-xyw-5.2.diff @@ -0,0 +1,99 @@ +--- a/dmenu.1 2022-10-04 10:36:58.000000000 -0700 ++++ b/dmenu.1 2024-03-23 19:40:27.116453289 -0700 +@@ -8,6 +8,12 @@ + .IR lines ] + .RB [ \-m + .IR monitor ] ++.RB [ \-x ++.IR xoffset ] ++.RB [ \-y ++.IR yoffset ] ++.RB [ \-z ++.IR width ] + .RB [ \-p + .IR prompt ] + .RB [ \-fn +@@ -54,6 +60,24 @@ + dmenu is displayed on the monitor number supplied. Monitor numbers are starting + from 0. + .TP ++.BI \-x " xoffset" ++dmenu is placed at this offset measured from the left side of the monitor. ++Can be negative. ++If option ++.B \-m ++is present, the measurement will use the given monitor. ++.TP ++.BI \-y " yoffset" ++dmenu is placed at this offset measured from the top of the monitor. If the ++.B \-b ++option is used, the offset is measured from the bottom. Can be negative. ++If option ++.B \-m ++is present, the measurement will use the given monitor. ++.TP ++.BI \-z " width" ++sets the width of the dmenu window. ++.TP + .BI \-p " prompt" + defines the prompt to be displayed to the left of the input field. + .TP +--- a/dmenu.c 2022-10-04 10:36:58.000000000 -0700 ++++ b/dmenu.c 2024-03-23 19:39:53.173081139 -0700 +@@ -37,6 +37,9 @@ + static char text[BUFSIZ] = ""; + static char *embed; + static int bh, mw, mh; ++static int dmx = 0; /* put dmenu at this x offset */ ++static int dmy = 0; /* put dmenu at this y offset (measured from the bottom if topbar is 0) */ ++static unsigned int dmw = 0; /* make dmenu this wide */ + static int inputw = 0, promptw; + static int lrpad; /* sum of left and right padding */ + static size_t cursor; +@@ -658,9 +661,9 @@ + if (INTERSECT(x, y, 1, 1, info[i]) != 0) + break; + +- x = info[i].x_org; +- y = info[i].y_org + (topbar ? 0 : info[i].height - mh); +- mw = info[i].width; ++ 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);; + XFree(info); + } else + #endif +@@ -668,9 +671,9 @@ + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); +- x = 0; +- y = topbar ? 0 : wa.height - mh; +- mw = wa.width; ++ x = dmx; ++ y = topbar ? dmy : wa.height - mh - dmy; ++ mw = (dmw>0 ? dmw : wa.width); + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = mw / 3; /* input width: ~33% of monitor width */ +@@ -711,6 +714,7 @@ + usage(void) + { + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" ++ " [-x xoffset] [-y yoffset] [-z width]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); + } + +@@ -737,6 +741,12 @@ + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); ++ else if (!strcmp(argv[i], "-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], "-z")) /* make dmenu this wide */ ++ dmw = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ diff --git a/dmenu/stest.c b/dmenu/stest.c index 7a7b0bc..e27d3a5 100644 --- a/dmenu/stest.c +++ b/dmenu/stest.c @@ -84,7 +84,7 @@ main(int argc, char *argv[]) if (!argc) { /* read list from stdin */ while ((n = getline(&line, &linesiz, stdin)) > 0) { - if (n && line[n - 1] == '\n') + if (line[n - 1] == '\n') line[n - 1] = '\0'; test(line, line); } diff --git a/dmenu/util.c b/dmenu/util.c index fe044fc..975735b 100644 --- a/dmenu/util.c +++ b/dmenu/util.c @@ -1,4 +1,5 @@ /* See LICENSE file for copyright and license details. */ +#include <errno.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -6,6 +7,25 @@ #include "util.h" +void +die(const char *fmt, ...) +{ + va_list ap; + int saved_errno; + + saved_errno = errno; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') + fprintf(stderr, " %s", strerror(saved_errno)); + fputc('\n', stderr); + + exit(1); +} + void * ecalloc(size_t nmemb, size_t size) { @@ -16,20 +36,8 @@ ecalloc(size_t nmemb, size_t size) 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); +int +startswith(const char* prefix, const char* str) +{ + return strncmp(prefix, str, strlen(prefix)) == 0; } diff --git a/dmenu/util.h b/dmenu/util.h index f633b51..6db39c8 100644 --- a/dmenu/util.h +++ b/dmenu/util.h @@ -3,6 +3,8 @@ #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)) +#define LENGTH(X) (sizeof (X) / sizeof (X)[0]) void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size); +int startswith(const char* prefix, const char* str); |
