sites

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

commit b6df034e735fb382f52a5f445551a830d8c5d63e
parent f59a8f3702933fc603571f5ed61c27a4735b1643
Author: Max Schillinger <maxschillinger@web.de>
Date:   Mon,  4 Nov 2024 12:05:50 +0100

[dmenu][patch][png_images] Add patch

This patch allows to show (preview) images in dmenu. It depends on the
libspng library.

Diffstat:
Atools.suckless.org/dmenu/patches/png_images/dmenu-png-images-5.3.diff | 448+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools.suckless.org/dmenu/patches/png_images/index.md | 38++++++++++++++++++++++++++++++++++++++
2 files changed, 486 insertions(+), 0 deletions(-)

diff --git a/tools.suckless.org/dmenu/patches/png_images/dmenu-png-images-5.3.diff b/tools.suckless.org/dmenu/patches/png_images/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/tools.suckless.org/dmenu/patches/png_images/index.md b/tools.suckless.org/dmenu/patches/png_images/index.md @@ -0,0 +1,38 @@ +png images +========== + +Description +----------- +Preview PNG images using [libspng](https://libspng.org/). + +Lines like `PNG_IMAGE:/path/to/image.png` will be replaced with a preview of +the given image file. The prefix (`PNG_IMAGE:`) can be changed via the `-ip` +flag. An empty prefix string is possible, too. + +The image preview is taken from the top left corner of the image. For vertical +menus, the height is limited to _N_ pixels provided via `-is N` or the height +of two text lines otherwise. For horizontal menus, the preview height equals +the bar height. The image width is limited to _N_ pixels provided via `-is N` +or the height of eight text lines otherwise. + +Example +------- + +Select a [greenclip](https://github.com/erebe/greenclip) clipboard entry with +image previews: + + greenclip print | grep . \ + | sed -E 's|^(image/png )(.*)|\1/tmp/greenclip/\2.png|' \ + | ./dmenu -i -fn 'monospace:size=14' -ip 'image/png ' -p clipboard -l 23 -is 120 \ + | sed -E 's|^(image/png )/tmp/greenclip/(-?[0-9]+)\.png$|\1\2|' \ + | xargs -r -d'\n' -I '{}' greenclip print '{}' + +![dmenu png images screenshot](https://maximilian-schillinger.de/img/screenshot_dmenu_libspng.png) + +Download +-------- +* [dmenu-png-images-5.3.diff](dmenu-png-images-5.3.diff) (2024-11-04) ([mirror @ sr.ht](https://git.sr.ht/~maxgyver83/dmenu/tree/image-support-libspng)) + +Authors +------- +* Max Schillinger - <maxschillinger@web.de>