Patched version of dmenu to render images in the option selector
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

354 lines
10 KiB

diff --git a/config.def.h b/config.def.h
index 1edb647..b787687 100644
--- a/config.def.h
+++ b/config.def.h
@@ -16,8 +16,20 @@ static const char *colors[SchemeLast][2] = {
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lines = 0;
+/* -h option; minimum height of a menu line */
+static unsigned int lineheight = 100;
+static unsigned int promptheight = 22;
+static unsigned int min_lineheight = 8;
+
/*
* Characters not considered part of a word while deleting words
* for example: " /?\"&[]"
*/
static const char worddelimiters[] = " ";
+
+/* filters to apply to images */
+static Filter filters[] = {
+ {"\\.ff$", "cat"},
+ {"\\.ff.bz2$", "bunzip2"},
+ {"\\.[a-z0-9]+$", "2ff"},
+};
diff --git a/dmenu.c b/dmenu.c
index 571bc35..766e180 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -1,6 +1,12 @@
/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
#include <locale.h>
+#include <math.h>
+#include <regex.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -20,6 +26,7 @@
#include "util.h"
/* macros */
+#define LEN(a) (sizeof(a) / sizeof(a)[0])
#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \
* MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
#define LENGTH(X) (sizeof X / sizeof X[0])
@@ -34,6 +41,24 @@ struct item {
int out;
};
+typedef enum {
+ NONE = 0,
+ SCALED = 1,
+} imgstate;
+
+typedef struct {
+ unsigned char *buf;
+ unsigned int bufwidth, bufheight;
+ imgstate state;
+ XImage *ximg;
+ int numpasses;
+} Image;
+
+typedef struct {
+ char *regex;
+ char *bin;
+} Filter;
+
static char text[BUFSIZ] = "";
static char *embed;
static int bh, mw, mh;
@@ -130,9 +155,177 @@ cistrstr(const char *h, const char *n)
return NULL;
}
+void ffscale(Image *img) {
+ unsigned int x, y;
+ unsigned int width = img->ximg->width;
+ unsigned int height = img->ximg->height;
+ char *newBuf = img->ximg->data;
+ unsigned char *ibuf;
+ unsigned int jdy = img->ximg->bytes_per_line / 4 - width;
+ unsigned int dx = (img->bufwidth << 10) / width;
+
+ for (y = 0; y < height; y++) {
+ unsigned int bufx = img->bufwidth / width;
+ ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3];
+
+ for (x = 0; x < width; x++) {
+ *newBuf++ = (ibuf[(bufx >> 10) * 3 + 2]);
+ *newBuf++ = (ibuf[(bufx >> 10) * 3 + 1]);
+ *newBuf++ = (ibuf[(bufx >> 10) * 3 + 0]);
+ newBuf++;
+ bufx += dx;
+ }
+ newBuf += jdy;
+ }
+}
+
+void ffprepare(Image *img, Drw *drw) {
+ int scr = XDefaultScreen(drw->dpy);
+ int depth = DefaultDepth(drw->dpy, scr);
+ int width = lineheight;
+ int height = lineheight;
+
+ if (800 * img->bufheight > lineheight * img->bufwidth)
+ width = img->bufwidth * lineheight / img->bufheight;
+ else
+ height = img->bufheight * lineheight / img->bufwidth;
+
+ if (depth < 24)
+ die("sent: Display color depths < 24 not supported");
+
+ if (!(img->ximg =
+ XCreateImage(drw->dpy, DefaultVisual(drw->dpy, drw->screen), depth,
+ ZPixmap, 0, NULL, width, height, 32, 0)))
+ die("sent: Unable to create XImage");
+
+ img->ximg->data = ecalloc(height, img->ximg->bytes_per_line);
+ if (!XInitImage(img->ximg))
+ die("sent: Unable to initiate XImage");
+
+ ffscale(img);
+ img->state |= SCALED;
+}
+
+void drw_image(Drw *drw, Image *img, int x, int y) {
+ XPutImage(drw->dpy, drw->drawable, drw->gc, img->ximg, 0, 0, x, y,
+ img->ximg->width, img->ximg->height);
+ XFlush(drw->dpy);
+}
+
+int filter(int fd, const char *cmd) {
+ int fds[2];
+
+ if (pipe(fds) < 0)
+ die("sent: Unable to create pipe:");
+
+ switch (fork()) {
+ case -1:
+ die("sent: Unable to fork:");
+ case 0:
+ dup2(fd, 0);
+ dup2(fds[1], 1);
+ close(fds[0]);
+ close(fds[1]);
+ execlp("sh", "sh", "-c", cmd, (char *)0);
+ fprintf(stderr, "sent: execlp sh -c '%s': %s\n", cmd, strerror(errno));
+ _exit(1);
+ }
+ close(fds[1]);
+ return fds[0];
+}
+
+Image *parse_image(char *filename) {
+ Image *img;
+ uint32_t y, x;
+ uint16_t *row;
+ uint8_t opac, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b;
+ size_t rowlen, off, nbytes, i;
+ ssize_t count;
+ unsigned char hdr[16];
+ char *bin = NULL;
+ regex_t regex;
+ int fdin, fdout;
+
+ for (i = 0; i < LEN(filters); i++) {
+ if (regcomp(&regex, filters[i].regex,
+ REG_NOSUB | REG_EXTENDED | REG_ICASE)) {
+ fprintf(stderr, "sent: Invalid regex '%s'\n", filters[i].regex);
+ continue;
+ }
+ if (!regexec(&regex, filename, 0, NULL, 0)) {
+ bin = filters[i].bin;
+ regfree(&regex);
+ break;
+ }
+ regfree(&regex);
+ }
+ if (!bin)
+ die("sent: Unable to find matching filter for '%s'", filename);
+
+ if ((fdin = open(filename, O_RDONLY)) < 0)
+ die("sent: Unable to open '%s':", filename);
+
+ if ((fdout = filter(fdin, bin)) < 0)
+ die("sent: Unable to filter '%s':", filename);
+ close(fdin);
+
+ if (read(fdout, hdr, 16) != 16)
+ die("sent: Unable to read filtered file '%s':", filename);
+ if (memcmp("farbfeld", hdr, 8))
+ die("sent: Filtered file '%s' has no valid farbfeld header", filename);
+
+ img = ecalloc(1, sizeof(Image));
+ img->bufwidth = ntohl(*(uint32_t *)&hdr[8]);
+ img->bufheight = ntohl(*(uint32_t *)&hdr[12]);
+
+ if (img->buf)
+ free(img->buf);
+ /* internally the image is stored in 888 format */
+ img->buf = ecalloc(img->bufwidth * img->bufheight, strlen("888"));
+
+ /* scratch buffer to read row by row */
+ rowlen = img->bufwidth * 2 * strlen("RGBA");
+ row = ecalloc(1, rowlen);
+
+ /* extract window background color channels for transparency */
+ bg_r = (scheme[SchemeNorm][ColBg].pixel >> 16) % 256;
+ bg_g = (scheme[SchemeNorm][ColBg].pixel >> 8) % 256;
+ bg_b = (scheme[SchemeNorm][ColBg].pixel >> 0) % 256;
+
+ for (off = 0, y = 0; y < img->bufheight; y++) {
+ nbytes = 0;
+ while (nbytes < rowlen) {
+ count = read(fdout, (char *)row + nbytes, rowlen - nbytes);
+ if (count < 0)
+ die("sent: Unable to read from pipe:");
+ nbytes += count;
+ }
+ for (x = 0; x < rowlen / 2; x += 4) {
+ fg_r = ntohs(row[x + 0]) / 257;
+ fg_g = ntohs(row[x + 1]) / 257;
+ fg_b = ntohs(row[x + 2]) / 257;
+ opac = ntohs(row[x + 3]) / 257;
+ /* blend opaque part of image data with window background color to
+ * emulate transparency */
+ img->buf[off++] = (fg_r * opac + bg_r * (255 - opac)) / 255;
+ img->buf[off++] = (fg_g * opac + bg_g * (255 - opac)) / 255;
+ img->buf[off++] = (fg_b * opac + bg_b * (255 - opac)) / 255;
+ }
+ }
+
+ free(row);
+ close(fdout);
+
+ return img;
+}
+
static int
drawitem(struct item *item, int x, int y, int w)
{
+ char splitStrings[10][100];
+ int i, j, cnt;
+ Image *im;
+
if (item == sel)
drw_setscheme(drw, scheme[SchemeSel]);
else if (item->out)
@@ -140,7 +333,32 @@ 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);
+ j = 0;
+ cnt = 0;
+ for (i = 0; i <= (strlen(item->text)); i++) {
+ // if space or NULL found, assign NULL into splitStrings[cnt]
+ if (item->text[i] == '\t' || item->text[i] == '\0') {
+ splitStrings[cnt][j] = '\0';
+ cnt++; // for next word
+ j = 0; // for next word, init index to 0
+ } else {
+ splitStrings[cnt][j] = item->text[i];
+ j++;
+ }
+ }
+ for (i = 0; i < cnt; i++)
+ if (splitStrings[i][0] == '@') {
+ memmove(splitStrings[i], splitStrings[i] + 1, strlen(splitStrings[i]));
+ im = parse_image(splitStrings[i]);
+ if (!(im->state & SCALED))
+ ffprepare(im, drw);
+
+ drw_image(drw, im, x, y);
+ } else
+ drw_text(drw, x + im->ximg->width + 4, y, w, bh, lrpad / 2,
+ splitStrings[i], 0);
+
+ return 0;
}
static void
@@ -148,30 +366,36 @@ drawmenu(void)
{
unsigned int curpos;
struct item *item;
- int x = 0, y = 0, w;
+ int x = 0, y = 0, fh = drw->fonts->h, w;
drw_setscheme(drw, scheme[SchemeNorm]);
drw_rect(drw, 0, 0, mw, mh, 1, 1);
if (prompt && *prompt) {
drw_setscheme(drw, scheme[SchemeSel]);
- x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
+ x = drw_text(drw, x, 0, promptw, promptheight, lrpad / 2, prompt, 0);
}
/* draw input field */
w = (lines > 0 || !matches) ? mw - x : inputw;
drw_setscheme(drw, scheme[SchemeNorm]);
- drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
+ drw_text(drw, x, 0, w, promptheight, lrpad / 2, text, 0);
curpos = TEXTW(text) - TEXTW(&text[cursor]);
if ((curpos += lrpad / 2 - 1) < w) {
drw_setscheme(drw, scheme[SchemeNorm]);
- drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+ drw_rect(drw, x + curpos, 2 + (promptheight - fh) / 2, 2, fh - 4, 1, 0);
}
+ int first = 0;
if (lines > 0) {
/* draw vertical list */
for (item = curr; item != next; item = item->right)
- drawitem(item, x, y += bh, mw - x);
+ if (first == 0) {
+ drawitem(item, x, y += promptheight, mw - x);
+ first = 1;
+ } else {
+ drawitem(item, x, y += bh, mw - x);
+ }
} else if (matches) {
/* draw horizontal list */
x += inputw;
@@ -630,8 +854,10 @@ setup(void)
/* calculate menu geometry */
bh = drw->fonts->h + 2;
- lines = MAX(lines, 0);
- mh = (lines + 1) * bh;
+ bh = MAX(bh, lineheight); /* make a menu line AT LEAST 'lineheight' tall */
+ lines = MAX(lines, 0);
+ mh = lines * bh + promptheight;
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
#ifdef XINERAMA
i = 0;
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
@@ -738,7 +964,10 @@ main(int argc, char *argv[])
/* these options take one argument */
else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
lines = atoi(argv[++i]);
- else if (!strcmp(argv[i], "-m"))
+ else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */
+ lineheight = atoi(argv[++i]);
+ lineheight = MAX(lineheight, min_lineheight);
+ } else if (!strcmp(argv[i], "-m"))
mon = atoi(argv[++i]);
else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */
prompt = argv[++i];