diff --git a/config.h b/config.h index a3e4f95..ca7c903 100644 --- a/config.h +++ b/config.h @@ -18,3 +18,6 @@ static int separator_pixels = 3; /* space around separator */ /* geometry of the right-pointing isoceles triangle for submenus */ static const int triangle_width = 3; static const int triangle_height = 7; + +/* sum of padding around both sides of the image */ +static const int imgpadding = 8; diff --git a/config.mk b/config.mk index f86aa34..0ffc8c5 100644 --- a/config.mk +++ b/config.mk @@ -14,8 +14,8 @@ FREETYPELIB = -lfontconfig -lXft #FREETYPEINC = $(X11INC)/freetype2 # includes and libs -INCS = -I${X11INC} -I${FREETYPEINC} -LIBS = -L${X11LIB} -L${FREETYPELIB} -lX11 +INCS = -I/usr/local/include -I${X11INC} -I${FREETYPEINC} +LIBS = -L/usr/local/lib -L${X11LIB} -L${FREETYPELIB} -lX11 -lImlib2 # flags CPPFLAGS = diff --git a/xmenu.1 b/xmenu.1 index d114668..5201032 100644 --- a/xmenu.1 +++ b/xmenu.1 @@ -13,17 +13,21 @@ and outputs the item selected to stdout. Each item read from stdin has the following format: .IP .EX -ITEM := [TABS] [LABEL [TABS OUTPUT]] NEWLINE +ITEM := [TABS] [[IMAGE TABS] LABEL [TABS OUTPUT]] NEWLINE .EE .PP That means that each item is composed by -tabs, followed by a label, followed by more tabs, followed by an output, +tabs, followed by an optional image specification, followed by tabs +followed by a label, followed by more tabs, followed by an output, and ended by a newline. Brackets group optional elements. .IP The initial tabs indicate the menu hierarchy: items indented with a tab is shown in a submenu of the preceding item not indented. An item without initial tabs is a top-level item. .IP +The image is a string of the form "IMG:/path/to/image.png". +It specifies a image to be shown as icon at the left of the entry. +.IP The label is the string that will be shown as a item in the menu. An item without label is considered a separator and is drawn as a thin line in the menu separating the item above from the item below. @@ -104,14 +108,14 @@ creating a command to be run by the shell. cat < #include #include +#include #define PROGNAME "xmenu" #define ITEMPREV 0 @@ -45,12 +46,14 @@ struct Geometry { struct Item { char *label; /* string to be drawed on menu */ char *output; /* string to be outputed when item is clicked */ + char *file; /* filename of the image */ int y; /* item y position relative to menu */ int h; /* item height */ size_t labellen; /* strlen(label) */ struct Item *prev; /* previous item */ struct Item *next; /* next item */ struct Menu *submenu; /* submenu spawned by clicking on item */ + Imlib_Image image; }; /* menu structure */ @@ -71,9 +74,9 @@ static void getresources(void); static void getcolor(const char *s, XftColor *color); static void setupdc(void); static void calcgeom(struct Geometry *geom); -static struct Item *allocitem(const char *label, const char *output); +static struct Item *allocitem(const char *label, const char *output, char *file); static struct Menu *allocmenu(struct Menu *parent, struct Item *list, unsigned level); -static struct Menu *buildmenutree(unsigned level, const char *label, const char *output); +static struct Menu *buildmenutree(unsigned level, const char *label, const char *output, char *file); static struct Menu *parsestdin(void); static void calcmenu(struct Geometry *geom, struct Menu *menu); static void grabpointer(void); @@ -129,6 +132,13 @@ main(int argc, char *argv[]) rootwin = RootWindow(dpy, screen); colormap = DefaultColormap(dpy, screen); + /* imlib2 stuff */ + imlib_set_cache_size(2048 * 1024); + imlib_context_set_dither(1); + imlib_context_set_display(dpy); + imlib_context_set_visual(visual); + imlib_context_set_colormap(colormap); + /* setup */ getresources(); setupdc(); @@ -247,7 +257,7 @@ calcgeom(struct Geometry *geom) /* allocate an item */ static struct Item * -allocitem(const char *label, const char *output) +allocitem(const char *label, const char *output, char *file) { struct Item *item; @@ -266,6 +276,12 @@ allocitem(const char *label, const char *output) err(1, "strdup"); } } + if (file == NULL) { + item->file = NULL; + } else { + if ((item->file = strdup(file)) == NULL) + err(1, "strdup"); + } item->y = 0; item->h = 0; if (item->label == NULL) @@ -274,6 +290,7 @@ allocitem(const char *label, const char *output) item->labellen = strlen(item->label); item->next = NULL; item->submenu = NULL; + item->image = NULL; return item; } @@ -314,7 +331,7 @@ allocmenu(struct Menu *parent, struct Item *list, unsigned level) /* build the menu tree */ static struct Menu * -buildmenutree(unsigned level, const char *label, const char *output) +buildmenutree(unsigned level, const char *label, const char *output, char *file) { static struct Menu *prevmenu = NULL; /* menu the previous item was added to */ static struct Menu *rootmenu = NULL; /* menu to be returned */ @@ -324,7 +341,7 @@ buildmenutree(unsigned level, const char *label, const char *output) unsigned i; /* create the item */ - curritem = allocitem(label, output); + curritem = allocitem(label, output, file); /* put the item in the menu tree */ if (prevmenu == NULL) { /* there is no menu yet */ @@ -377,7 +394,7 @@ parsestdin(void) { struct Menu *rootmenu; char *s, buf[BUFSIZ]; - char *label, *output; + char *file, *label, *output; unsigned level = 0; rootmenu = NULL; @@ -390,6 +407,13 @@ parsestdin(void) s = level + buf; label = strtok(s, "\t\n"); + /* get the filename */ + file = NULL; + if (label != NULL && strncmp(label, "IMG:", 4) == 0) { + file = label + 4; + label = strtok(NULL, "\t\n"); + } + /* get the output */ output = strtok(NULL, "\n"); if (output == NULL) { @@ -399,12 +423,36 @@ parsestdin(void) output++; } - rootmenu = buildmenutree(level, label, output); + rootmenu = buildmenutree(level, label, output, file); } return rootmenu; } +/* load and scale image */ +static Imlib_Image +loadimage(const char *file, int size) +{ + Imlib_Image image; + int width; + int height; + int imgsize; + + image = imlib_load_image(file); + if (image == NULL) + errx(1, "cannot load image %s", file); + + imlib_context_set_image(image); + + width = imlib_image_get_width(); + height = imlib_image_get_height(); + imgsize = MIN(width, height); + + image = imlib_create_cropped_scaled_image(0, 0, imgsize, imgsize, size, size); + + return image; +} + /* recursivelly calculate menu geometry and set window hints */ static void calcmenu(struct Geometry *geom, struct Menu *menu) @@ -430,8 +478,12 @@ calcmenu(struct Geometry *geom, struct Menu *menu) XftTextExtentsUtf8(dpy, dc.font, (XftChar8 *)item->label, item->labellen, &ext); - labelwidth = ext.xOff + dc.font->height * 2; + labelwidth = ext.xOff + dc.font->height * 2 + imgpadding; menu->w = MAX(menu->w, labelwidth); + + /* create image */ + if (item->file != NULL) + item->image = loadimage(item->file, dc.font->height); } /* calculate menu's x and y positions */ @@ -621,7 +673,7 @@ drawitem(struct Menu *menu, struct Item *item, XftColor *color) { int x, y; - x = dc.font->height; + x = dc.font->height + imgpadding; y = item->y + item->h/2 + dc.font->ascent/2 - 1; XSetForeground(dpy, dc.gc, color[ColorFG].pixel); XftDrawStringUtf8(menu->draw, &color[ColorFG], dc.font, @@ -629,8 +681,8 @@ drawitem(struct Menu *menu, struct Item *item, XftColor *color) /* draw triangle, if item contains a submenu */ if (item->submenu != NULL) { - x = menu->w - dc.font->height/2 - triangle_width/2; - y = item->y + item->h/2 - triangle_height/2 - 1; + x = menu->w - (dc.font->height - triangle_width) / 2; + y = item->y + (item->h - triangle_height) / 2; XPoint triangle[] = { {x, y}, @@ -642,6 +694,15 @@ drawitem(struct Menu *menu, struct Item *item, XftColor *color) XFillPolygon(dpy, menu->pixmap, dc.gc, triangle, LEN(triangle), Convex, CoordModeOrigin); } + + /* draw image */ + if (item->file != NULL) { + x = imgpadding / 2; + y = item->y + (item->h - dc.font->height) / 2; + imlib_context_set_drawable(menu->pixmap); + imlib_context_set_image(item->image); + imlib_render_image_on_drawable(x, y); + } } /* draw items of the current menu and of its ancestors */ @@ -831,6 +892,13 @@ freemenu(struct Menu *menu) if (tmp->label != tmp->output) free(tmp->label); free(tmp->output); + if (tmp->file != NULL) { + free(tmp->file); + if (item->image != NULL) { + imlib_context_set_image(item->image); + imlib_free_image(); + } + } free(tmp); } diff --git a/xmenu.sh b/xmenu.sh index abd9a41..db08041 100755 --- a/xmenu.sh +++ b/xmenu.sh @@ -2,7 +2,7 @@ cat <