sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

dmenu-png-images-5.3.diff (13922B)


      1 From 743d86e56e0c1eb4255a08fe338db03752cc99e7 Mon Sep 17 00:00:00 2001
      2 From: Max Schillinger <maxschillinger@web.de>
      3 Date: Fri, 1 Nov 2024 08:58:49 +0100
      4 Subject: [PATCH] Support PNG images using libspng
      5 
      6 ---
      7  config.mk |   2 +-
      8  dmenu.c   |  62 +++++++++++++++---
      9  drw.c     | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
     10  drw.h     |   5 ++
     11  util.c    |   6 ++
     12  util.h    |   1 +
     13  6 files changed, 254 insertions(+), 11 deletions(-)
     14 
     15 diff --git a/config.mk b/config.mk
     16 index 137f7c8..3217090 100644
     17 --- a/config.mk
     18 +++ b/config.mk
     19 @@ -21,7 +21,7 @@ FREETYPEINC = /usr/include/freetype2
     20  
     21  # includes and libs
     22  INCS = -I$(X11INC) -I$(FREETYPEINC)
     23 -LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
     24 +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lspng
     25  
     26  # flags
     27  CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
     28 diff --git a/dmenu.c b/dmenu.c
     29 index 804da64..b0e4109 100644
     30 --- a/dmenu.c
     31 +++ b/dmenu.c
     32 @@ -38,11 +38,14 @@ static char *embed;
     33  static int bh, mw, mh;
     34  static int inputw = 0, promptw;
     35  static int lrpad; /* sum of left and right padding */
     36 +static int tbpad; /* sum of top and bottom padding for images */
     37  static size_t cursor;
     38  static struct item *items = NULL;
     39  static struct item *matches, *matchend;
     40  static struct item *prev, *curr, *next, *sel;
     41  static int mon = -1, screen;
     42 +static char *image_prefix = "PNG_IMAGE:";
     43 +static int image_size = -1; /* in pixels */
     44  
     45  static Atom clip, utf8;
     46  static Display *dpy;
     47 @@ -58,12 +61,26 @@ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
     48  static char *(*fstrstr)(const char *, const char *) = strstr;
     49  
     50  static unsigned int
     51 -textw_clamp(const char *str, unsigned int n)
     52 +textw_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh)
     53  {
     54 -	unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
     55 +	unsigned int w;
     56 +	if (startswith(image_prefix, str) &&
     57 +			(w = drw_getimagewidth_clamp(drw, str + strlen(image_prefix), maxw, maxh)))
     58 +		return MIN(w + lrpad, n);
     59 +	w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
     60  	return MIN(w, n);
     61  }
     62  
     63 +static unsigned int
     64 +texth_clamp(const char *str, unsigned int n, unsigned int maxw, unsigned int maxh)
     65 +{
     66 +	unsigned int h;
     67 +	if (startswith(image_prefix, str) &&
     68 +			(h = drw_getimageheight_clamp(drw, str + strlen(image_prefix), maxw, maxh)))
     69 +		return MIN(h + tbpad, n);
     70 +	return MIN(bh, n);
     71 +}
     72 +
     73  static void
     74  appenditem(struct item *item, struct item **list, struct item **last)
     75  {
     76 @@ -83,15 +100,19 @@ calcoffsets(void)
     77  	int i, n;
     78  
     79  	if (lines > 0)
     80 -		n = lines * bh;
     81 +		n = mh - bh;
     82  	else
     83  		n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
     84  	/* calculate which items will begin the next page and previous page */
     85  	for (i = 0, next = curr; next; next = next->right)
     86 -		if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
     87 +		if ((i += (lines > 0)
     88 +					? texth_clamp(next->text, n, mw - lrpad, image_size)
     89 +					: textw_clamp(next->text, n, image_size, bh)) > n)
     90  			break;
     91  	for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
     92 -		if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
     93 +		if ((i += (lines > 0)
     94 +					? texth_clamp(prev->left->text, n, mw - lrpad, image_size)
     95 +					: textw_clamp(prev->left->text, n, image_size, bh)) > n)
     96  			break;
     97  }
     98  
     99 @@ -139,7 +160,18 @@ drawitem(struct item *item, int x, int y, int w)
    100  	else
    101  		drw_setscheme(drw, scheme[SchemeNorm]);
    102  
    103 -	return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
    104 +	int vertical = lines > 0;
    105 +	if (startswith(image_prefix, item->text)) {
    106 +		char *path = item->text + strlen(image_prefix);
    107 +		unsigned int image_width = vertical ? w - lrpad : image_size;
    108 +		unsigned int image_height = vertical ? image_size : bh;
    109 +		drw_image(drw, &x, &y, &image_width, &image_height,
    110 +		          lrpad, vertical ? tbpad : 0, path, vertical);
    111 +		if (image_width && image_height)
    112 +			return vertical ? y : x;
    113 +	}
    114 +	int nextx = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
    115 +	return vertical ? y + bh : nextx;
    116  }
    117  
    118  static void
    119 @@ -169,8 +201,9 @@ drawmenu(void)
    120  
    121  	if (lines > 0) {
    122  		/* draw vertical list */
    123 +		y = bh;
    124  		for (item = curr; item != next; item = item->right)
    125 -			drawitem(item, x, y += bh, mw - x);
    126 +			y = drawitem(item, x, y, mw - x);
    127  	} else if (matches) {
    128  		/* draw horizontal list */
    129  		x += inputw;
    130 @@ -181,7 +214,7 @@ drawmenu(void)
    131  		}
    132  		x += w;
    133  		for (item = curr; item != next; item = item->right)
    134 -			x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">")));
    135 +			x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"), image_size, bh));
    136  		if (next) {
    137  			w = TEXTW(">");
    138  			drw_setscheme(drw, scheme[SchemeNorm]);
    139 @@ -635,7 +668,10 @@ setup(void)
    140  	/* calculate menu geometry */
    141  	bh = drw->fonts->h + 2;
    142  	lines = MAX(lines, 0);
    143 -	mh = (lines + 1) * bh;
    144 +	/* default values for image_size */
    145 +	if (image_size < 0)
    146 +		image_size = (lines > 0) ? 2 * bh : 8 * bh;
    147 +	mh = bh + ((lines > 0) ? MAX(lines * bh, image_size) : 0);
    148  #ifdef XINERAMA
    149  	i = 0;
    150  	if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
    151 @@ -715,7 +751,8 @@ static void
    152  usage(void)
    153  {
    154  	die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
    155 -	    "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
    156 +	    "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n"
    157 +	    "             [-ip image_prefix] [-is image_size]");
    158  }
    159  
    160  int
    161 @@ -757,6 +794,10 @@ main(int argc, char *argv[])
    162  			colors[SchemeSel][ColFg] = argv[++i];
    163  		else if (!strcmp(argv[i], "-w"))   /* embedding window id */
    164  			embed = argv[++i];
    165 +		else if (!strcmp(argv[i], "-ip"))  /* image prefix */
    166 +			image_prefix = argv[++i];
    167 +		else if (!strcmp(argv[i], "-is"))  /* max. image preview size (height or width) */
    168 +			image_size = atoi(argv[++i]);
    169  		else
    170  			usage();
    171  
    172 @@ -775,6 +816,7 @@ main(int argc, char *argv[])
    173  	if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
    174  		die("no fonts could be loaded.");
    175  	lrpad = drw->fonts->h;
    176 +	tbpad = lrpad / 2;
    177  
    178  #ifdef __OpenBSD__
    179  	if (pledge("stdio rpath", NULL) == -1)
    180 diff --git a/drw.c b/drw.c
    181 index c41e6af..20c2125 100644
    182 --- a/drw.c
    183 +++ b/drw.c
    184 @@ -4,12 +4,24 @@
    185  #include <string.h>
    186  #include <X11/Xlib.h>
    187  #include <X11/Xft/Xft.h>
    188 +#include <spng.h>
    189  
    190  #include "drw.h"
    191  #include "util.h"
    192  
    193  #define UTF_INVALID 0xFFFD
    194  
    195 +struct image_item {
    196 +	const char *path;
    197 +	int width;
    198 +	int height;
    199 +	char *buf;
    200 +	Pixmap pixmap;
    201 +	struct image_item *next;
    202 +};
    203 +
    204 +static struct image_item *images = NULL;
    205 +
    206  static int
    207  utf8decode(const char *s_in, long *u, int *err)
    208  {
    209 @@ -382,6 +394,163 @@ no_match:
    210  	return x + (render ? w : 0);
    211  }
    212  
    213 +static struct image_item *
    214 +load_image(Drw *drw, unsigned int maxw, unsigned int maxh, const char *path)
    215 +{
    216 +	FILE *png;
    217 +	spng_ctx *ctx = NULL;
    218 +	int ret = 0;
    219 +	struct spng_ihdr ihdr;
    220 +	struct spng_plte plte = {0};
    221 +	struct spng_row_info row_info = {0};
    222 +	char *spng_buf;
    223 +	int fmt = SPNG_FMT_RGBA8;
    224 +	int crop_width;
    225 +	int crop_height;
    226 +
    227 +	struct image_item *image = ecalloc(1, sizeof(struct image_item));
    228 +	image->path = path;
    229 +	image->next = images;
    230 +	images = image;
    231 +
    232 +	png = fopen(path, "rb");
    233 +	if (png == NULL) {
    234 +		fprintf(stderr, "error opening input file %s\n", path);
    235 +		return NULL;
    236 +	}
    237 +
    238 +	/* Create a context */
    239 +	ctx = spng_ctx_new(0);
    240 +	if (ctx == NULL) {
    241 +		fprintf(stderr, "%s: spng_ctx_new() failed\n", path);
    242 +		return NULL;
    243 +	}
    244 +
    245 +	/* Ignore and don't calculate chunk CRC's */
    246 +	spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE);
    247 +
    248 +	/* Set memory usage limits for storing standard and unknown chunks,
    249 +	   this is important when reading untrusted files! */
    250 +	size_t limit = 1024 * 1024 * 64;
    251 +	spng_set_chunk_limits(ctx, limit, limit);
    252 +
    253 +	spng_set_png_file(ctx, png);
    254 +
    255 +	ret = spng_get_ihdr(ctx, &ihdr);
    256 +	if (ret) {
    257 +		fprintf(stderr, "%s: spng_get_ihdr() error: %s\n", path, spng_strerror(ret));
    258 +		return NULL;
    259 +	}
    260 +
    261 +	ret = spng_get_plte(ctx, &plte);
    262 +	if (ret && ret != SPNG_ECHUNKAVAIL) {
    263 +		fprintf(stderr, "%s: spng_get_plte() error: %s\n", path, spng_strerror(ret));
    264 +		return NULL;
    265 +	}
    266 +
    267 +	size_t image_size, bytes_per_row; /* size in bytes, not in pixels */
    268 +
    269 +	ret = spng_decoded_image_size(ctx, fmt, &image_size);
    270 +	if (ret)
    271 +		return NULL;
    272 +
    273 +	spng_buf = malloc(image_size);
    274 +	if (!spng_buf)
    275 +		return NULL;
    276 +
    277 +	ret = spng_decode_image(ctx, NULL, 0, fmt, SPNG_DECODE_PROGRESSIVE);
    278 +	if (ret) {
    279 +		fprintf(stderr, "%s: progressive spng_decode_image() error: %s\n",
    280 +		        path, spng_strerror(ret));
    281 +		return NULL;
    282 +	}
    283 +
    284 +	/* ihdr.height will always be non-zero if spng_get_ihdr() succeeds */
    285 +	bytes_per_row = image_size / ihdr.height;
    286 +	crop_width = MIN(ihdr.width, maxw);
    287 +	crop_height = MIN(ihdr.height, maxh);
    288 +
    289 +	do {
    290 +		ret = spng_get_row_info(ctx, &row_info);
    291 +		if (ret)
    292 +			break;
    293 +		ret = spng_decode_row(ctx, spng_buf + row_info.row_num * bytes_per_row, bytes_per_row);
    294 +	} while (!ret && row_info.row_num < crop_height);
    295 +
    296 +	if (ret != SPNG_EOI && row_info.row_num < crop_height)
    297 +		fprintf(stderr, "%s: progressive decode error: %s\n", path, spng_strerror(ret));
    298 +
    299 +	image->buf = calloc(ihdr.width * crop_height * 4, sizeof(char));
    300 +	for (int i = 0; i < ihdr.width * crop_height; i++) {
    301 +		/* RGBA to BGRA */
    302 +		image->buf[i*4+2] = spng_buf[i*4+0];
    303 +		image->buf[i*4+1] = spng_buf[i*4+1];
    304 +		image->buf[i*4+0] = spng_buf[i*4+2];
    305 +		image->buf[i*4+3] = spng_buf[i*4+3];
    306 +	}
    307 +	image->width = crop_width;
    308 +	image->height = crop_height;
    309 +
    310 +	XImage *img = XCreateImage(drw->dpy, CopyFromParent, DefaultDepth(drw->dpy, drw->screen),
    311 +	                           ZPixmap, 0, image->buf, ihdr.width, crop_height, 32, 0);
    312 +	image->pixmap = XCreatePixmap(drw->dpy, drw->root, crop_width, crop_height, 24);
    313 +	XPutImage(drw->dpy, image->pixmap, drw->gc, img, 0, 0, 0, 0, crop_width, crop_height);
    314 +	spng_ctx_free(ctx);
    315 +	fclose(png);
    316 +	return image;
    317 +}
    318 +
    319 +void
    320 +drw_image(Drw *drw, int *x, int *y, unsigned int *w, unsigned int *h,
    321 +          unsigned int lrpad, unsigned int tbpad, const char *path, int vertical)
    322 +{
    323 +	/* *x and *y refer to box position including padding,
    324 +	 * *w and *h are the maximum image width and height without padding */
    325 +	struct image_item *image = NULL;
    326 +	int render = *x || *y;
    327 +	int crop_width, crop_height;
    328 +
    329 +	// find path in images
    330 +	for (struct image_item *item = images; item != NULL; item = item->next) {
    331 +		if (!strcmp(item->path, path)) {
    332 +			image = item;
    333 +			if (!image->buf)
    334 +				goto file_error;
    335 +			break;
    336 +		}
    337 +	}
    338 +
    339 +	if (!image && !(image = load_image(drw, *w, *h, path)))
    340 +		goto file_error;
    341 +
    342 +	if (!render) {
    343 +		*w = image->width;
    344 +		*h = image->height;
    345 +		return;
    346 +	}
    347 +
    348 +	crop_width = MIN(image->width, *w);
    349 +	crop_height = MIN(image->height, *h);
    350 +	if (vertical)
    351 +		*h = crop_height;
    352 +	else
    353 +		*w = crop_width;
    354 +
    355 +	XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
    356 +	XFillRectangle(drw->dpy, drw->drawable, drw->gc, *x, *y, *w + lrpad, *h + tbpad);
    357 +	XCopyArea(drw->dpy, image->pixmap, drw->drawable, drw->gc, 0, 0,
    358 +	          crop_width, crop_height, *x + lrpad/2, *y + tbpad/2);
    359 +
    360 +	if (vertical)
    361 +		*y += *h + tbpad;
    362 +	else
    363 +		*x += *w + lrpad;
    364 +	return;
    365 +
    366 +file_error:
    367 +	*w = *h = 0;
    368 +}
    369 +
    370  void
    371  drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
    372  {
    373 @@ -424,6 +593,26 @@ drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w,
    374  		*h = font->h;
    375  }
    376  
    377 +unsigned int
    378 +drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh)
    379 +{
    380 +	int x = 0, y = 0;
    381 +	unsigned int w = maxw, h = maxh;
    382 +	if (drw && path && maxw && maxh)
    383 +		drw_image(drw, &x, &y, &w, &h, 0, 0, path, 0);
    384 +	return MIN(maxw, w);
    385 +}
    386 +
    387 +unsigned int
    388 +drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh)
    389 +{
    390 +	int x = 0, y = 0;
    391 +	unsigned int w = maxw, h = maxh;
    392 +	if (drw && path && maxw && maxh)
    393 +		drw_image(drw, &x, &y, &w, &h, 0, 0, path, 1);
    394 +	return MIN(maxh, h);
    395 +}
    396 +
    397  Cur *
    398  drw_cur_create(Drw *drw, int shape)
    399  {
    400 diff --git a/drw.h b/drw.h
    401 index fd7631b..330722d 100644
    402 --- a/drw.h
    403 +++ b/drw.h
    404 @@ -38,6 +38,10 @@ unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
    405  unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
    406  void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
    407  
    408 +/* Image abstraction */
    409 +unsigned int drw_getimagewidth_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh);
    410 +unsigned int drw_getimageheight_clamp(Drw *drw, const char *path, unsigned int maxw, unsigned int maxh);
    411 +
    412  /* Colorscheme abstraction */
    413  void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
    414  Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
    415 @@ -53,6 +57,7 @@ void drw_setscheme(Drw *drw, Clr *scm);
    416  /* Drawing functions */
    417  void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
    418  int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
    419 +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);
    420  
    421  /* Map functions */
    422  void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
    423 diff --git a/util.c b/util.c
    424 index 8e26a51..975735b 100644
    425 --- a/util.c
    426 +++ b/util.c
    427 @@ -35,3 +35,9 @@ ecalloc(size_t nmemb, size_t size)
    428  		die("calloc:");
    429  	return p;
    430  }
    431 +
    432 +int
    433 +startswith(const char* prefix, const char* str)
    434 +{
    435 +	return strncmp(prefix, str, strlen(prefix)) == 0;
    436 +}
    437 diff --git a/util.h b/util.h
    438 index c0a50d4..6db39c8 100644
    439 --- a/util.h
    440 +++ b/util.h
    441 @@ -7,3 +7,4 @@
    442  
    443  void die(const char *fmt, ...);
    444  void *ecalloc(size_t nmemb, size_t size);
    445 +int startswith(const char* prefix, const char* str);
    446 -- 
    447 2.47.0
    448