From abe10f04b5ea79f9d652e7d21ac0600ffa9a74b4 Mon Sep 17 00:00:00 2001 From: speedie Date: Sat, 25 Feb 2023 03:46:26 +0100 Subject: [PATCH] add very wip image support --- LICENSE | 1 + host.mk | 10 +- options.h | 3 + options.mk | 2 +- spmenu.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++++- toggle.h | 8 ++ 6 files changed, 309 insertions(+), 5 deletions(-) create mode 100644 toggle.h diff --git a/LICENSE b/LICENSE index a9805fb..09d7443 100644 --- a/LICENSE +++ b/LICENSE @@ -8,6 +8,7 @@ MIT/X Consortium License © 2009 Markus Schnalke © 2009 Evan Gates © 2010-2012 Connor Lane Smith +@ 2013 Jari Vetoniemi © 2014-2022 Hiltjo Posthuma © 2015-2019 Quentin Rameau © 2021-2023 speedie diff --git a/host.mk b/host.mk index 5758102..c0a0f45 100644 --- a/host.mk +++ b/host.mk @@ -28,6 +28,12 @@ PANGOXFT_CONF = pangoxft # xrender XRENDERLIBS = -lXrender +# imlib2 +IMLIB2LIBS = -lImlib2 + +# openssl +OPENSSL_CONF = openssl + # OpenBSD (uncomment) #FREETYPEINC = $(X11INC)/freetype2 @@ -39,5 +45,5 @@ XRENDERLIBS = -lXrender CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) CFLAGS = -std=c99 -pedantic -Wall -Ofast $(INCS) $(CPPFLAGS) LDFLAGS = $(LIBS) -INCS = -I$(X11INC) -I$(FREETYPEINC) `pkg-config --cflags $(XFT_CONF) $(PANGO_CONF) $(PANGOXFT_CONF)` $(BDINC) -LIBS = -L$(X11LIB) $(X11LIBS) $(XINERAMALIBS) $(FREETYPELIBS) $(XRENDERLIBS) -lm `pkg-config --libs $(XFT_CONF) $(PANGO_CONF) $(PANGOXFT_CONF)` $(BDLIBS) +INCS = -I$(X11INC) -I$(FREETYPEINC) `pkg-config --cflags $(XFT_CONF) $(PANGO_CONF) $(PANGOXFT_CONF) $(OPENSSL_CONF)` $(BDINC) +LIBS = -L$(X11LIB) $(X11LIBS) $(XINERAMALIBS) $(FREETYPELIBS) $(XRENDERLIBS) -lm `pkg-config --libs $(XFT_CONF) $(PANGO_CONF) $(PANGOXFT_CONF) $(OPENSSL_CONF)` $(BDLIBS) $(IMLIB2LIBS) diff --git a/options.h b/options.h index 7330cf3..fd2ff31 100644 --- a/options.h +++ b/options.h @@ -15,6 +15,9 @@ static int menupaddingh = 0; /* Horizontal padding of bar (in pixels) */ static int minwidth = 500; /* Minimum width */ static int centered = 0; /* Whether or not to center spmenu by default */ +/* Image options */ +static int generatecache = 0; /* Generate image cache by default */ + static int colorsupport = 1; /* Support 256 colors? Otherwise the default 16 colors will be used. */ /* Window border options */ diff --git a/options.mk b/options.mk index 3ed0d82..4aafd03 100644 --- a/options.mk +++ b/options.mk @@ -6,4 +6,4 @@ VERSION = 0.1 # includes and libs INCS = -I$(X11INC) -I$(FREETYPEINC) `pkg-config --cflags xft pango pangoxft` -LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) ${XRENDERLIBS} -lm `pkg-config --libs xft pango pangoxft` +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) $(XRENDERLIBS) $(IMLIB2LIBS) -lm `pkg-config --libs xft pango pangoxft` diff --git a/spmenu.c b/spmenu.c index 75907a8..a40b044 100644 --- a/spmenu.c +++ b/spmenu.c @@ -9,6 +9,15 @@ #include #include +#include "toggle.h" + +#if USEIMAGE +#include +#include +#include +#include +#endif + #ifdef FRIBIDI #define USERTL 1 #else @@ -77,6 +86,9 @@ static char text[BUFSIZ] = ""; struct item { char *text; +#if USEIMAGE + char *image; +#endif struct item *left, *right; double distance; }; @@ -95,6 +107,11 @@ typedef struct { const Arg arg; } Key; +#if USEIMAGE +static int imagesize = 86; +static Imlib_Image image = NULL; +#endif + static char numbers[NUMBERSBUFSIZE] = ""; static char *embed; static int numlockmask = 0; @@ -109,6 +126,7 @@ static int lrpad; /* sum of left and right padding */ static int vp; /* vertical padding for bar */ static int sp; /* side padding for bar */ static size_t cursor; +static unsigned int selected = 0; static struct item *items = NULL, *backup_items; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel; @@ -179,6 +197,171 @@ static char * cistrstr(const char *s, const char *sub); static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp; static char *(*fstrstr)(const char *, const char *) = cistrstr; +#if USEIMAGE +void +createifnexist(const char *dir) +{ + if(access(dir, F_OK) == 0) + return; + + if(errno == EACCES) + fprintf(stderr, "spmenu: no access to create directory: %s\n", dir); + + if(mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) + fprintf(stderr, "spmenu: failed to create directory: %s\n", dir); +} + +void +createifnexist_rec(const char *dir) +{ + char *buf, *s = (char*)dir, *bs; + + if(!(buf = malloc(strlen(s)+1))) + return; + + memset(buf, 0, strlen(s)+1); + + for(bs = buf; *s; ++s, ++bs) { + if(*s == '/' && *buf) + createifnexist(buf); + + *bs = *s; + } + + free(buf); +} + +void +loadimage(const char *file, int *width, int *height) +{ + image = imlib_load_image(file); + if (!image) + return; + + imlib_context_set_image(image); + + *width = imlib_image_get_width(); + *height = imlib_image_get_height(); +} + +void +scaleimage(int width, int height) +{ + int nwidth, nheight; + float aspect = 1.0f; + + if (width > height) + aspect = (float)imagesize/width; + else + aspect = (float)imagesize/height; + + nwidth = width * aspect; + nheight = height * aspect; + + if(nwidth == width && nheight == height) + return; + + image = imlib_create_cropped_scaled_image(0,0,width,height,nwidth,nheight); + imlib_free_image(); + + if (!image) + return; + + imlib_context_set_image(image); +} + +void +loadimagecache(const char *file, int *width, int *height) +{ + int slen = 0, i; + unsigned char digest[MD5_DIGEST_LENGTH]; + char md5[MD5_DIGEST_LENGTH*2+1]; + char *xdg_cache, *home = NULL, *dsize, *buf; + struct passwd *pw = NULL; + + /* just load and don't store or try cache */ + if(imagesize > 256) { + loadimage(file, width, height); + return; + } + + /* try find image from cache first */ + if(!(xdg_cache = getenv("XDG_CACHE_HOME"))) { + if(!(home = getenv("HOME")) && (pw = getpwuid(getuid()))) + home = pw->pw_dir; + if(!home) { + fprintf(stderr, "spmenu: could not find home directory"); + return; + } + } + + /* which cache do we try? */ + dsize = "normal"; + if (imagesize > 128) + dsize = "large"; + + slen = snprintf(NULL, 0, "file://%s", file)+1; + + if(!(buf = malloc(slen))) { + fprintf(stderr, "spmenu: out of memory"); + return; + } + + /* calculate md5 from path */ + sprintf(buf, "file://%s", file); + MD5((unsigned char*)buf, slen, digest); + + free(buf); + + for(i = 0; i < MD5_DIGEST_LENGTH; ++i) + sprintf(&md5[i*2], "%02x", (unsigned int)digest[i]); + + /* path for cached thumbnail */ + if (xdg_cache) + slen = snprintf(NULL, 0, "%s/thumbnails/%s/%s.png", xdg_cache, dsize, md5)+1; + else + slen = snprintf(NULL, 0, "%s/.thumbnails/%s/%s.png", home, dsize, md5)+1; + + if(!(buf = malloc(slen))) { + fprintf(stderr, "out of memory"); + return; + } + + if (xdg_cache) + sprintf(buf, "%s/thumbnails/%s/%s.png", xdg_cache, dsize, md5); + else + sprintf(buf, "%s/.thumbnails/%s/%s.png", home, dsize, md5); + + loadimage(buf, width, height); + + if (image && *width < imagesize && *height < imagesize) { + imlib_free_image(); + image = NULL; + } else if(image && (*width > imagesize || *height > imagesize)) { + scaleimage(*width, *height); + } + + /* we are done */ + if (image) { + free(buf); + return; + } + + /* we din't find anything from cache, or it was just wrong */ + loadimage(file, width, height); + if (!image) { + free(buf); + return; + } + + scaleimage(*width, *height); + imlib_image_set_format("png"); + createifnexist_rec(buf); + imlib_save_image(buf); + free(buf); +} +#endif + void appenditem(struct item *item, struct item **list, struct item **last) { @@ -244,6 +427,12 @@ cleanup(void) { size_t i; +#if USEIMAGE + if (image) { + imlib_free_image(); + image = NULL; + } +#endif XUngrabKey(dpy, AnyKey, AnyModifier, root); for (i = 0; i < SchemeLast; i++) free(scheme[i]); @@ -456,6 +645,10 @@ drawmenu(void) if (!hidematchcount) recalculatenumbers(); if (lines > 0) { +#if USEIMAGE + if (imagesize) + x += 4 + imagesize; +#endif /* draw grid */ int i = 0; for (item = curr; item != next; item = item->right, i++) @@ -1366,7 +1559,10 @@ readstdin(void) char buf[sizeof text], *p; size_t i, imax = 0, itemsiz = 0; unsigned int tmpmax = 0; - if(passwd){ + int w, h; + char *limg = NULL; + + if (passwd){ inputw = lines = 0; return; } @@ -1388,11 +1584,49 @@ readstdin(void) inputw = tmpmax; imax = i; } + +#if USEIMAGE + if(!strncmp("IMG:", items[i].text, strlen("IMG:"))) { + if(!(items[i].image = malloc(strlen(items[i].text)+1))) + fprintf(stderr, "spmenu: cannot malloc %lu bytes\n", strlen(items[i].text)); + if(sscanf(items[i].text, "IMG:%[^\t]", items[i].image)) { + if(!(items[i].image = realloc(items[i].image, strlen(items[i].image)+1))) + fprintf(stderr, "spmenu: cannot realloc %lu bytes\n", strlen(items[i].image)+1); + items[i].text += strlen("IMG:")+strlen(items[i].image)+1; + } else { + free(items[i].image); + items[i].image = NULL; + } + } else { + items[i].image = NULL; + } + + if (generatecache && imagesize <= 256 && items[i].image && strcmp(items[i].image, limg?limg:"")) { + loadimagecache(items[i].image, &w, &h); + fprintf(stdout, "-!- Generating thumbnail for: %s\n", items[i].image); + } + + if(items[i].image) + limg = items[i].image; +#endif } - if (items) + + if (items) { +#if USEIMAGE + items[i].image = NULL; +#endif items[i].text = NULL; + } +#if USEIMAGE + if (!limg) + imagesize = 0; +#endif inputw = items ? TEXTWM(items[imax].text) : 0; lines = MIN(lines, i); +#if USEIMAGE + if(lines * drw->font->h < imagesize) + lines = imagesize/drw->font->h+2; +#endif } void @@ -1447,6 +1681,10 @@ void run(void) { XEvent ev; +#if USEIMAGE + int width = 0, height = 0; + char *limg = NULL; +#endif while (!XNextEvent(dpy, &ev)) { if (XFilterEvent(&ev, win)) @@ -1481,6 +1719,26 @@ run(void) XRaiseWindow(dpy, win); break; } + +#if USEIMAGE + if (!lines) + continue; + + if (sel && sel->image && strcmp(sel->image, limg ? limg : "")) { + if (imagesize) + loadimagecache(sel->image, &width, &height); + } else if ((!sel || !sel->image) && image) { + imlib_free_image(); + image = NULL; + } if (image && imagesize) { + imlib_render_image_on_drawable(imagesize-width, (imagesize-height)/2+bh); + + } if (sel) { + limg = sel->image; + } else { + limg = NULL; + } +#endif } } @@ -1643,8 +1901,20 @@ setup(void) /* might be faster in some instances, most of the time unnecessary */ if (!accuratewidth) inputw = MIN(inputw, mw/3); + match(); + #if USEIMAGE + /* + for(i = 1; i < selected; ++i) { + if(sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + } + */ + #endif + /* create menu window */ swa.override_redirect = managed ? False : True; swa.background_pixel = 0; @@ -1671,6 +1941,16 @@ setup(void) XSetClassHint(dpy, win, &ch); XChangeProperty(dpy, win, types, XA_ATOM, 32, PropModeReplace, (unsigned char *) &dock, 1); + #if USEIMAGE + imlib_set_cache_size(8192 * 1024); + imlib_context_set_blend(1); + imlib_context_set_dither(1); + imlib_set_color_usage(128); + imlib_context_set_display(dpy); + imlib_context_set_visual(visual); + imlib_context_set_colormap(cmap); + imlib_context_set_drawable(win); + #endif /* input methods */ if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) @@ -1701,6 +1981,7 @@ usage(void) "spmenu -l Set line count to stdin\n" "spmenu -h Set spmenu line height to \n" "spmenu -g Set the number of grids to \n" + "spmenu -gc Generate image cache\n" "spmenu -rw Enable relative input width\n" "spmenu -nrw Disable relative input width\n" "spmenu -f Grabs keyboard before reading stdin\n" @@ -1734,6 +2015,7 @@ usage(void) "spmenu -hp Set the horizontal padding\n" "spmenu -la Set the left arrow to \n" "spmenu -ra Set the right arrow to \n" + "spmenu -is Image size\n" "spmenu -wm Spawn spmenu as a window manager controlled client/window. Useful for testing\n" "spmenu -v Print spmenu version to stdout\n" "\n", stdout); @@ -1844,6 +2126,8 @@ main(int argc, char *argv[]) } else if (!strcmp(argv[i], "-i")) { /* case-sensitive item matching, for compatibility reasons */ fstrncmp = strncasecmp; fstrstr = cistrstr; + } else if (!strcmp(argv[i], "-gc")) { /* generate image cache */ + generatecache = 1; } else if (!strcmp(argv[i], "-wm")) { /* display as managed wm window */ managed = 1; } else if (!strcmp(argv[i], "-na")) { /* disable alpha */ @@ -1923,6 +2207,8 @@ main(int argc, char *argv[]) colors[SchemeMenu][ColFg] = argv[++i]; colors[SchemeInput][ColBg] = argv[++i]; colors[SchemePrompt][ColFg] = argv[++i]; + } else if (!strcmp(argv[i], "-is")) { /* image size */ + imagesize = atoi(argv[++i]); /* spmenu colors */ } else if (!strcmp(argv[i], "-nif")) { /* normal item foreground color */ diff --git a/toggle.h b/toggle.h new file mode 100644 index 0000000..86aa966 --- /dev/null +++ b/toggle.h @@ -0,0 +1,8 @@ +/* Toggle patches + * This header allows you to enable/disable patches that can break compability or decrease performance with certain OSes or configurations. + * + * If it's disabled (set to 0), it will not be compiled in with spmenu. This keeps the binary small and may make the experience better. + */ + +/* Image related patches */ +#define USEIMAGE 1 /* Include image support. Requires imlib2 */