~gnkv/dmenu

9e9ae690ce15733228476d83431c9a8f0072692a — Eduard Ganiukov 4 years ago ba1a347 master
dmenu: init personal fork
14 files changed, 213 insertions(+), 39 deletions(-)

A .gitignore
M Makefile
M README
A config.h
M config.mk
A dmenu-pass
A dmenu-path
A dmenu-pinentry
A dmenu-run
A dmenu-run-recent
M dmenu.1
M dmenu.c
D dmenu_path
D dmenu_run
A .gitignore => .gitignore +3 -0
@@ 0,0 1,3 @@
*.o
dmenu
stest

M Makefile => Makefile +12 -6
@@ 34,7 34,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-run dmenu-run-recent dmenu-pass dmenu-pinentry stest.1 $(SRC)\
		dmenu-$(VERSION)
	tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION)
	gzip dmenu-$(VERSION).tar


@@ 42,10 42,13 @@ 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-run dmenu-run-recent dmenu-pass dmenu-pinentry stest $(DESTDIR)$(PREFIX)/bin
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu-path
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu-run
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu-run-recent
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu-pass
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu-pinentry
	chmod 755 $(DESTDIR)$(PREFIX)/bin/stest
	mkdir -p $(DESTDIR)$(MANPREFIX)/man1
	sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1


@@ 55,8 58,11 @@ install: all

uninstall:
	rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\
		$(DESTDIR)$(PREFIX)/bin/dmenu_path\
		$(DESTDIR)$(PREFIX)/bin/dmenu_run\
		$(DESTDIR)$(PREFIX)/bin/dmenu-path\
		$(DESTDIR)$(PREFIX)/bin/dmenu-run\
		$(DESTDIR)$(PREFIX)/bin/dmenu-run-recent\
		$(DESTDIR)$(PREFIX)/bin/dmenu-pass\
		$(DESTDIR)$(PREFIX)/bin/dmenu-pinentry\
		$(DESTDIR)$(PREFIX)/bin/stest\
		$(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
		$(DESTDIR)$(MANPREFIX)/man1/stest.1

M README => README +8 -0
@@ 22,3 22,11 @@ Afterwards enter the following command to build and install dmenu
Running dmenu
-------------
See the man page for details.

Customizations
--------------
Patches:
1. https://tools.suckless.org/dmenu/patches/line-height/
2. https://tools.suckless.org/dmenu/patches/center/
3. https://tools.suckless.org/dmenu/patches/border/
4. https://tools.suckless.org/dmenu/patches/password/

A config.h => config.h +28 -0
@@ 0,0 1,28 @@
/* See LICENSE file for copyright and license details. */
/* Default settings; can be overriden by command line. */

static int topbar = 1;                      /* -b  option; if 0, dmenu appears at bottom     */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
    "monospace:size=12:antialias=true:autohint=true",
};
static const char *prompt = ">>";      /* -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" },
};

static unsigned int lines      = 20; /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lineheight = 24; /* -h option; minimum height of a menu line     */
static int centered = 1;             /* -c option; centers dmenu on screen */
static int min_width = 500;          /* minimum width when centered */

static const unsigned int border_width = 4;

/*
 * Characters not considered part of a word while deleting words
 * for example: " /?\"&[]"
 */
static const char worddelimiters[] = " ";

M config.mk => config.mk +5 -2
@@ 5,8 5,11 @@ VERSION = 5.2
PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man

X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib
# X11INC = /usr/X11R6/include
# X11LIB = /usr/X11R6/lib

X11INC = /usr/include/X11
X11LIB = /usr/lib/X11

# Xinerama, comment if you don't want it
XINERAMALIBS  = -lXinerama

A dmenu-pass => dmenu-pass +45 -0
@@ 0,0 1,45 @@
#!/usr/bin/env bash

# set -e
set -o pipefail

shopt -s nullglob globstar

typeit=0
if [[ $1 == "--type" ]]; then
	typeit=1
	shift
fi

if [[ -n $WAYLAND_DISPLAY ]]; then
	dmenu=dmenu-wl
	xdotool="ydotool type --file -"
elif [[ -n $DISPLAY ]]; then
	dmenu=dmenu
	xdotool="xdotool type --clearmodifiers --file -"
else
	echo "Error: No Wayland or X11 display detected" >&2
	exit 1
fi

prefix=${PASSWORD_STORE_DIR-~/.password-store}
password_files=( "$prefix"/**/*.gpg )
password_files=( "${password_files[@]#"$prefix"/}" )
password_files=( "${password_files[@]%.gpg}" )

password=$(printf '%s\n' "${password_files[@]}" | "$dmenu" "$@")

[[ -n $password ]] || exit

if [[ $typeit -eq 0 ]]; then
	pass show -c "$password" 2>/dev/null
else
	pass show "$password" | { IFS= read -r pass; printf %s "$pass"; } | $xdotool
fi

if [ "$?" -eq "0" ]
then
	notify-send "Password store" "# Password $password copied for 45s."
else
	notify-send "Password store" "# FAILED to copy password."
fi

A dmenu-path => dmenu-path +12 -0
@@ 0,0 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.

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 | uniq

A dmenu-pinentry => dmenu-pinentry +12 -0
@@ 0,0 1,12 @@
#!/bin/bash

printf "OK Pleased to meet you\n"

while read stdin; do
	case $stdin in
    	*BYE*) break ;;
		*SETDESC*) KEYNAME=${stdin#*:%0A%22}; KEYNAME=${KEYNAME%\%22\%0A*}; KEYID=${stdin#*ID }; KEYID=${KEYID%,*}; printf "OK\n" ;;
		*GETPIN*) printf "D $(dmenu -P -p "PIN:")\nOK\n" ;;
		*) printf "OK\n"
	esac
done

A dmenu-run => dmenu-run +2 -0
@@ 0,0 1,2 @@
#!/usr/bin/env bash
dmenu-path | dmenu "$@" | ${SHELL:-"/bin/sh"} &

A dmenu-run-recent => dmenu-run-recent +21 -0
@@ 0,0 1,21 @@
#!/usr/bin/env bash

TERMINAL="st -e"
CACHE_DIR=${XDG_CACHE_HOME:-"$HOME/.cache/dmenu"}
CACHE="$CACHE_DIR/recent"

touch "$CACHE"

MOST_USED=$(sort "$CACHE" | grep -v "^$" | sort | uniq -c | awk '{print $2}')
RUN=$((echo "$MOST_USED"; dmenu-path | grep -vxF "$MOST_USED") | dmenu -i "$@")

if [ "$RUN" == "" ]; then
	exit 0;
fi

(echo "$RUN"; head -n 99 "$CACHE") > "$CACHE.$$"
mv "$CACHE.$$" "$CACHE"
case "$RUN" in
	*\;) exec $(echo $TERMINAL "$RUN" | sed -e 's/;$//') ;;
	*)   exec "$RUN" ;;
esac

M dmenu.1 => dmenu.1 +7 -1
@@ 3,7 3,7 @@
dmenu \- dynamic menu
.SH SYNOPSIS
.B dmenu
.RB [ \-bfiv ]
.RB [ \-bfivP ]
.RB [ \-l
.IR lines ]
.RB [ \-m


@@ 47,9 47,15 @@ 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
.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.

M dmenu.c => dmenu.c +58 -15
@@ 21,7 21,7 @@

/* 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)))
                             && 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)



@@ 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;


@@ 96,6 96,15 @@ calcoffsets(void)
			break;
}

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

static void
cleanup(void)
{


@@ 148,7 157,8 @@ drawmenu(void)
{
	unsigned int curpos;
	struct item *item;
	int x = 0, y = 0, w;
	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);


@@ 160,12 170,17 @@ 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) {
		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) {


@@ 552,6 567,10 @@ readstdin(void)
	char *line = NULL;
	size_t i, junk, itemsiz = 0;
	ssize_t len;
	if (passwd) {
		inputw = lines = 0;
		return;
	}

	/* read each line from stdin and add it to the item list */
	for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++) {


@@ 634,8 653,10 @@ 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;
	promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#ifdef XINERAMA
	i = 0;
	if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {


@@ 662,9 683,16 @@ setup(void)
				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;
		if (centered) {
			mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width);
			x = info[i].x_org + ((info[i].width  - mw) / 2);
			y = info[i].y_org + ((info[i].height - mh) / 2);
		} else {
			x = info[i].x_org;
			y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
			mw = info[i].width;
		}

		XFree(info);
	} else
#endif


@@ 672,11 700,17 @@ setup(void)
		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;

		if (centered) {
			mw = MIN(MAX(max_textw() + promptw, min_width), wa.width);
			x = (wa.width  - mw) / 2;
			y = (wa.height - mh) / 2;
		} else {
			x = 0;
			y = topbar ? 0 : wa.height - mh;
			mw = wa.width;
		}
	}
	promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
	inputw = mw / 3; /* input width: ~33% of monitor width */
	match();



@@ 684,9 718,10 @@ setup(void)
	swa.override_redirect = True;
	swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
	swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
	win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
	win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
	                    CopyFromParent, CopyFromParent, CopyFromParent,
	                    CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
	XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
	XSetClassHint(dpy, win, &ch);




@@ 714,7 749,7 @@ setup(void)
static void
usage(void)
{
	die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
	die("usage: dmenu [-bfivP] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
	    "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
}



@@ 733,10 768,14 @@ main(int argc, char *argv[])
			topbar = 0;
		else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
			fast = 1;
		else if (!strcmp(argv[i], "-c"))   /* centers dmenu on screen */
			centered = 1;
		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 */


@@ 747,6 786,10 @@ main(int argc, char *argv[])
			prompt = argv[++i];
		else if (!strcmp(argv[i], "-fn"))  /* font or font set */
			fonts[0] = argv[++i];
		else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */
			lineheight = atoi(argv[++i]);
			lineheight = MAX(lineheight,8); /* reasonable default in case of value too small/negative */
		}
		else if (!strcmp(argv[i], "-nb"))  /* normal background color */
			colors[SchemeNorm][ColBg] = argv[++i];
		else if (!strcmp(argv[i], "-nf"))  /* normal foreground color */

D dmenu_path => dmenu_path +0 -13
@@ 1,13 0,0 @@
#!/bin/sh

cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
cache="$cachedir/dmenu_run"

[ ! -e "$cachedir" ] && mkdir -p "$cachedir"

IFS=:
if stest -dqr -n "$cache" $PATH; then
	stest -flx $PATH | sort -u | tee "$cache"
else
	cat "$cache"
fi

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