~kris/suckless

dmenu

541dead59a544fbd15390ad35f6412ca6ac64d32 — Kris Yotam 2 months ago 9d8d4f8
Add PNG_IMAGES_PATCH for inline image previews

Port of suckless.org png_images patch into flexipatch build.
Adds -ip (image prefix) and -is (image size) flags.
Items prefixed with PNG_IMAGE: render inline PNG thumbnails.
Requires libspng.
7 files changed, 342 insertions(+), 8 deletions(-)

M config.mk
M dmenu.c
M drw.c
M drw.h
M patches.h
M util.c
M util.h
M config.mk => config.mk +4 -1
@@ 33,9 33,12 @@ XRENDER = -lXrender
#PANGOINC = `pkg-config --cflags xft pango pangoxft`
#PANGOLIB = `pkg-config --libs xft pango pangoxft`

# PNG images patch / PNG_IMAGES_PATCH
SPNGLIBS = -lspng

# includes and libs
INCS = -I$(X11INC) -I$(FREETYPEINC) $(PANGOINC) $(BIDIINC)
LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm $(XRENDER) $(PANGOLIB) $(BIDILIBS)
LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm $(XRENDER) $(PANGOLIB) $(BIDILIBS) $(SPNGLIBS)

# flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) $(EXTRAFLAGS)

M dmenu.c => dmenu.c +114 -7
@@ 126,6 126,9 @@ static int inputw = 0, promptw;
static int passwd = 0;
#endif // PASSWORD_PATCH
static int lrpad; /* sum of left and right padding */
#if PNG_IMAGES_PATCH
static int tbpad; /* sum of top and bottom padding for images */
#endif // PNG_IMAGES_PATCH
#if BARPADDING_PATCH
static int vp; /* vertical padding for bar */
static int sp; /* side padding for bar */


@@ 138,6 141,10 @@ static struct item *items = NULL;
static struct item *matches, *matchend;
static struct item *prev, *curr, *next, *sel;
static int mon = -1, screen;
#if PNG_IMAGES_PATCH
static char *image_prefix = "PNG_IMAGE:";
static int image_size = -1; /* in pixels */
#endif // PNG_IMAGES_PATCH
#if PRINTINDEX_PATCH
static int print_index = 0;
#endif // PRINTINDEX_PATCH


@@ 185,12 192,34 @@ static Clr *scheme[SchemeLast];
#include "config.h"

static unsigned int
textw_clamp(const char *str, unsigned int n)
textw_clamp(const char *str, unsigned int n
#if PNG_IMAGES_PATCH
	, unsigned int maxw, unsigned int maxh
#endif // PNG_IMAGES_PATCH
)
{
	unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
	unsigned int w;
	#if PNG_IMAGES_PATCH
	if (startswith(image_prefix, str) &&
			(w = drw_getimagewidth_clamp(drw, str + strlen(image_prefix), maxw, maxh)))
		return MIN(w + lrpad, n);
	#endif // PNG_IMAGES_PATCH
	w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
	return MIN(w, n);
}

#if PNG_IMAGES_PATCH
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((unsigned int)bh, n);
}
#endif // PNG_IMAGES_PATCH

static void appenditem(struct item *item, struct item **list, struct item **last);
static void calcoffsets(void);
static void cleanup(void);


@@ 245,14 274,16 @@ calcoffsets(void)
	int i, n, rpad = 0;

	if (lines > 0) {
		#if GRID_PATCH
		#if PNG_IMAGES_PATCH
		n = mh - bh;
		#elif GRID_PATCH
		if (columns)
			n = lines * columns * bh;
		else
			n = lines * bh;
		#else
		n = lines * bh;
		#endif // GRID_PATCH
		#endif // PNG_IMAGES_PATCH / GRID_PATCH
	} else {
		#if NUMBERS_PATCH
		rpad = TEXTW(numbers);


@@ 265,10 296,22 @@ calcoffsets(void)
	}
	/* calculate which items will begin the next page and previous page */
	for (i = 0, next = curr; next; next = next->right)
		#if PNG_IMAGES_PATCH
		if ((i += (lines > 0)
					? texth_clamp(next->text, n, mw - lrpad, image_size)
					: textw_clamp(next->text, n, image_size, bh)) > n)
		#else
		if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
		#endif // PNG_IMAGES_PATCH
			break;
	for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
		#if PNG_IMAGES_PATCH
		if ((i += (lines > 0)
					? texth_clamp(prev->left->text, n, mw - lrpad, image_size)
					: textw_clamp(prev->left->text, n, image_size, bh)) > n)
		#else
		if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
		#endif // PNG_IMAGES_PATCH
			break;
}



@@ 471,6 514,23 @@ drawitem(struct item *item, int x, int y, int w)
	else
		drw_setscheme(drw, scheme[SchemeNorm]);

	#if PNG_IMAGES_PATCH
	{
		int vertical = lines > 0;
		if (startswith(image_prefix, text)) {
			char *path = 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) {
				r = vertical ? y : x;
				goto done_drawitem;
			}
		}
	}
	#endif // PNG_IMAGES_PATCH

	r = drw_text(drw
		#if EMOJI_HIGHLIGHT_PATCH
		, x + ((iscomment == 6) ? temppadding : 0)


@@ 502,6 562,9 @@ drawitem(struct item *item, int x, int y, int w)
	drawhighlights(item, x, y, w);
	#endif // EMOJI_HIGHLIGHT_PATCH
	#endif // HIGHLIGHT_PATCH
	#if PNG_IMAGES_PATCH
done_drawitem:
	#endif // PNG_IMAGES_PATCH
	return r;
}



@@ 717,6 780,19 @@ drawmenu(void)

		#else
		/* draw vertical list */
		#if PNG_IMAGES_PATCH
		y = bh;
		for (item = curr; item != next; item = item->right) {
			#if DYNAMIC_HEIGHT_PATCH
			i++;
			#endif // DYNAMIC_HEIGHT_PATCH
			#if VERTFULL_PATCH
			y = drawitem(item, 0, y, mw);
			#else
			y = drawitem(item, x, y, mw - x);
			#endif // VERTFULL_PATCH
		}
		#else
		for (item = curr; item != next; item = item->right) {
			#if DYNAMIC_HEIGHT_PATCH
			i++;


@@ 727,6 803,7 @@ drawmenu(void)
			drawitem(item, x, y += bh, mw - x);
			#endif // VERTFULL_PATCH
		}
		#endif // PNG_IMAGES_PATCH
		#if DYNAMIC_HEIGHT_PATCH
		XResizeWindow(dpy, win, mw, (i + 1) * bh);
		#endif // DYNAMIC_HEIGHT_PATCH


@@ 759,9 836,17 @@ drawmenu(void)
			stw = TEXTW(">");
			#endif // SYMBOLS_PATCH
			#if TSV_PATCH && !SEPARATOR_PATCH
			itw = textw_clamp(item->stext, mw - x - stw - rpad);
			itw = textw_clamp(item->stext, mw - x - stw - rpad
				#if PNG_IMAGES_PATCH
				, image_size, bh
				#endif // PNG_IMAGES_PATCH
			);
			#else
			itw = textw_clamp(item->text, mw - x - stw - rpad);
			itw = textw_clamp(item->text, mw - x - stw - rpad
				#if PNG_IMAGES_PATCH
				, image_size, bh
				#endif // PNG_IMAGES_PATCH
			);
			#endif // TSV_PATCH
			x = drawitem(item, x, 0, itw);
		}


@@ 1769,7 1854,13 @@ setup(void)
	bh = MAX(bh,lineheight);	/* make a menu line AT LEAST 'lineheight' tall */
	#endif // LINE_HEIGHT_PATCH
	lines = MAX(lines, 0);
	#if PNG_IMAGES_PATCH
	if (image_size < 0)
		image_size = (lines > 0) ? 2 * bh : 8 * bh;
	mh = bh + ((lines > 0) ? MAX(lines * bh, image_size) : 0);
	#else
	mh = (lines + 1) * bh;
	#endif // PNG_IMAGES_PATCH
	#if CENTER_PATCH && PANGO_PATCH
	promptw = (prompt && *prompt) ? TEXTWM(prompt) - lrpad / 4 : 0;
	#elif CENTER_PATCH


@@ 1880,7 1971,11 @@ setup(void)
		if (numwidthchecks || minstrlen < curstrlen) {
			numwidthchecks = MAX(numwidthchecks - 1, 0);
			minstrlen = MAX(minstrlen, curstrlen);
			if ((tmp = textw_clamp(item->text, mw/3)) > inputw) {
			if ((tmp = textw_clamp(item->text, mw/3
				#if PNG_IMAGES_PATCH
				, image_size, bh
				#endif // PNG_IMAGES_PATCH
			)) > inputw) {
				inputw = tmp;
				if (tmp == mw/3)
					break;


@@ 2085,6 2180,9 @@ usage(void)
		#if SEPARATOR_PATCH
		"\n             [-d separator] [-D separator]"
		#endif // SEPARATOR_PATCH
		#if PNG_IMAGES_PATCH
		"\n             [-ip image_prefix] [-is image_size]"
		#endif // PNG_IMAGES_PATCH
		"\n");
}



@@ 2351,6 2449,12 @@ main(int argc, char *argv[])
			insert(text, strlen(text));
		}
		#endif // INITIALTEXT_PATCH
		#if PNG_IMAGES_PATCH
		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]);
		#endif // PNG_IMAGES_PATCH
		else {
			usage();
		}


@@ 2402,6 2506,9 @@ main(int argc, char *argv[])
	#else
	lrpad = drw->fonts->h;
	#endif // PANGO_PATCH
	#if PNG_IMAGES_PATCH
	tbpad = lrpad / 2;
	#endif // PNG_IMAGES_PATCH

	#if BARPADDING_PATCH
	sp = sidepad;

M drw.c => drw.c +202 -0
@@ 6,6 6,11 @@
#include <X11/Xft/Xft.h>

#include "patches.h"

#if PNG_IMAGES_PATCH
#include <spng.h>
#endif // PNG_IMAGES_PATCH

#include "drw.h"
#include "util.h"



@@ 647,6 652,203 @@ no_match:
}
#endif // PANGO_PATCH

#if PNG_IMAGES_PATCH
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 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;
	}

	ctx = spng_ctx_new(0);
	if (ctx == NULL) {
		fprintf(stderr, "%s: spng_ctx_new() failed\n", path);
		fclose(png);
		return NULL;
	}

	spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE);

	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));
		spng_ctx_free(ctx);
		fclose(png);
		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));
		spng_ctx_free(ctx);
		fclose(png);
		return NULL;
	}

	size_t image_size, bytes_per_row;

	ret = spng_decoded_image_size(ctx, fmt, &image_size);
	if (ret) {
		spng_ctx_free(ctx);
		fclose(png);
		return NULL;
	}

	spng_buf = malloc(image_size);
	if (!spng_buf) {
		spng_ctx_free(ctx);
		fclose(png);
		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));
		free(spng_buf);
		spng_ctx_free(ctx);
		fclose(png);
		return NULL;
	}

	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 < (unsigned int)crop_height);

	if (ret != SPNG_EOI && row_info.row_num < (unsigned int)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 (unsigned int i = 0; i < ihdr.width * (unsigned int)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);
	free(spng_buf);
	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)
{
	struct image_item *image = NULL;
	int render = *x || *y;
	int crop_width, crop_height;

	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, (int)*w);
	crop_height = MIN(image->height, (int)*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;
}

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);
}
#endif // PNG_IMAGES_PATCH

void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
{

M drw.h => drw.h +7 -0
@@ 101,6 101,13 @@ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned in
int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
#endif // PANGO_PATCH

#if PNG_IMAGES_PATCH
/* 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);
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);
#endif // PNG_IMAGES_PATCH

/* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);


M patches.h => patches.h +3 -0
@@ 179,3 179,6 @@

/* Position and width options. */
#define XYW_PATCH 0

/* PNG image preview support using libspng. */
#define PNG_IMAGES_PATCH 1

M util.c => util.c +9 -0
@@ 4,6 4,7 @@
#include <stdlib.h>
#include <string.h>

#include "patches.h"
#include "util.h"

void


@@ 34,3 35,11 @@ ecalloc(size_t nmemb, size_t size)
		die("calloc:");
	return p;
}

#if PNG_IMAGES_PATCH
int
startswith(const char *prefix, const char *str)
{
	return strncmp(prefix, str, strlen(prefix)) == 0;
}
#endif // PNG_IMAGES_PATCH

M util.h => util.h +3 -0
@@ 11,3 11,6 @@

void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size);
#if PNG_IMAGES_PATCH
int startswith(const char *prefix, const char *str);
#endif // PNG_IMAGES_PATCH