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