| 1 | diff --git a/config.h b/config.h |
| 2 | index a3e4f95..ca7c903 100644 |
| 3 | --- a/config.h |
| 4 | +++ b/config.h |
| 5 | @@ -18,3 +18,6 @@ static int separator_pixels = 3; /* space around separator */ |
| 6 | /* geometry of the right-pointing isoceles triangle for submenus */ |
| 7 | static const int triangle_width = 3; |
| 8 | static const int triangle_height = 7; |
| 9 | + |
| 10 | +/* sum of padding around both sides of the image */ |
| 11 | +static const int imgpadding = 8; |
| 12 | diff --git a/config.mk b/config.mk |
| 13 | index f86aa34..0ffc8c5 100644 |
| 14 | --- a/config.mk |
| 15 | +++ b/config.mk |
| 16 | @@ -14,8 +14,8 @@ FREETYPELIB = -lfontconfig -lXft |
| 17 | #FREETYPEINC = $(X11INC)/freetype2 |
| 18 | |
| 19 | # includes and libs |
| 20 | -INCS = -I${X11INC} -I${FREETYPEINC} |
| 21 | -LIBS = -L${X11LIB} -L${FREETYPELIB} -lX11 |
| 22 | +INCS = -I/usr/local/include -I${X11INC} -I${FREETYPEINC} |
| 23 | +LIBS = -L/usr/local/lib -L${X11LIB} -L${FREETYPELIB} -lX11 -lImlib2 |
| 24 | |
| 25 | # flags |
| 26 | CPPFLAGS = |
| 27 | diff --git a/xmenu.1 b/xmenu.1 |
| 28 | index d114668..5201032 100644 |
| 29 | --- a/xmenu.1 |
| 30 | +++ b/xmenu.1 |
| 31 | @@ -13,17 +13,21 @@ and outputs the item selected to stdout. |
| 32 | Each item read from stdin has the following format: |
| 33 | .IP |
| 34 | .EX |
| 35 | -ITEM := [TABS] [LABEL [TABS OUTPUT]] NEWLINE |
| 36 | +ITEM := [TABS] [[IMAGE TABS] LABEL [TABS OUTPUT]] NEWLINE |
| 37 | .EE |
| 38 | .PP |
| 39 | That means that each item is composed by |
| 40 | -tabs, followed by a label, followed by more tabs, followed by an output, |
| 41 | +tabs, followed by an optional image specification, followed by tabs |
| 42 | +followed by a label, followed by more tabs, followed by an output, |
| 43 | and ended by a newline. Brackets group optional elements. |
| 44 | .IP |
| 45 | The initial tabs indicate the menu hierarchy: |
| 46 | items indented with a tab is shown in a submenu of the preceding item not indented. |
| 47 | An item without initial tabs is a top-level item. |
| 48 | .IP |
| 49 | +The image is a string of the form "IMG:/path/to/image.png". |
| 50 | +It specifies a image to be shown as icon at the left of the entry. |
| 51 | +.IP |
| 52 | The label is the string that will be shown as a item in the menu. |
| 53 | An item without label is considered a separator and is drawn as a thin line in the menu |
| 54 | separating the item above from the item below. |
| 55 | @@ -104,14 +108,14 @@ creating a command to be run by the shell. |
| 56 | |
| 57 | cat <<EOF | xmenu | sh & |
| 58 | Applications |
| 59 | - Web Browser firefox |
| 60 | - Image editor gimp |
| 61 | -Terminal (xterm) xterm |
| 62 | -Terminal (urxvt) urxvt |
| 63 | -Terminal (st) st |
| 64 | + IMG:./web.png Web Browser firefox |
| 65 | + Image editor gimp |
| 66 | +Terminal (xterm) xterm |
| 67 | +Terminal (urxvt) urxvt |
| 68 | +Terminal (st) st |
| 69 | |
| 70 | -Shutdown poweroff |
| 71 | -Reboot reboot |
| 72 | +Shutdown poweroff |
| 73 | +Reboot reboot |
| 74 | EOF |
| 75 | .EE |
| 76 | .PP |
| 77 | diff --git a/xmenu.c b/xmenu.c |
| 78 | index abab13d..d70c6b8 100644 |
| 79 | --- a/xmenu.c |
| 80 | +++ b/xmenu.c |
| 81 | @@ -8,6 +8,7 @@ |
| 82 | #include <X11/Xresource.h> |
| 83 | #include <X11/XKBlib.h> |
| 84 | #include <X11/Xft/Xft.h> |
| 85 | +#include <Imlib2.h> |
| 86 | |
| 87 | #define PROGNAME "xmenu" |
| 88 | #define ITEMPREV 0 |
| 89 | @@ -45,12 +46,14 @@ struct Geometry { |
| 90 | struct Item { |
| 91 | char *label; /* string to be drawed on menu */ |
| 92 | char *output; /* string to be outputed when item is clicked */ |
| 93 | + char *file; /* filename of the image */ |
| 94 | int y; /* item y position relative to menu */ |
| 95 | int h; /* item height */ |
| 96 | size_t labellen; /* strlen(label) */ |
| 97 | struct Item *prev; /* previous item */ |
| 98 | struct Item *next; /* next item */ |
| 99 | struct Menu *submenu; /* submenu spawned by clicking on item */ |
| 100 | + Imlib_Image image; |
| 101 | }; |
| 102 | |
| 103 | /* menu structure */ |
| 104 | @@ -71,9 +74,9 @@ static void getresources(void); |
| 105 | static void getcolor(const char *s, XftColor *color); |
| 106 | static void setupdc(void); |
| 107 | static void calcgeom(struct Geometry *geom); |
| 108 | -static struct Item *allocitem(const char *label, const char *output); |
| 109 | +static struct Item *allocitem(const char *label, const char *output, char *file); |
| 110 | static struct Menu *allocmenu(struct Menu *parent, struct Item *list, unsigned level); |
| 111 | -static struct Menu *buildmenutree(unsigned level, const char *label, const char *output); |
| 112 | +static struct Menu *buildmenutree(unsigned level, const char *label, const char *output, char *file); |
| 113 | static struct Menu *parsestdin(void); |
| 114 | static void calcmenu(struct Geometry *geom, struct Menu *menu); |
| 115 | static void grabpointer(void); |
| 116 | @@ -129,6 +132,13 @@ main(int argc, char *argv[]) |
| 117 | rootwin = RootWindow(dpy, screen); |
| 118 | colormap = DefaultColormap(dpy, screen); |
| 119 | |
| 120 | + /* imlib2 stuff */ |
| 121 | + imlib_set_cache_size(2048 * 1024); |
| 122 | + imlib_context_set_dither(1); |
| 123 | + imlib_context_set_display(dpy); |
| 124 | + imlib_context_set_visual(visual); |
| 125 | + imlib_context_set_colormap(colormap); |
| 126 | + |
| 127 | /* setup */ |
| 128 | getresources(); |
| 129 | setupdc(); |
| 130 | @@ -247,7 +257,7 @@ calcgeom(struct Geometry *geom) |
| 131 | |
| 132 | /* allocate an item */ |
| 133 | static struct Item * |
| 134 | -allocitem(const char *label, const char *output) |
| 135 | +allocitem(const char *label, const char *output, char *file) |
| 136 | { |
| 137 | struct Item *item; |
| 138 | |
| 139 | @@ -266,6 +276,12 @@ allocitem(const char *label, const char *output) |
| 140 | err(1, "strdup"); |
| 141 | } |
| 142 | } |
| 143 | + if (file == NULL) { |
| 144 | + item->file = NULL; |
| 145 | + } else { |
| 146 | + if ((item->file = strdup(file)) == NULL) |
| 147 | + err(1, "strdup"); |
| 148 | + } |
| 149 | item->y = 0; |
| 150 | item->h = 0; |
| 151 | if (item->label == NULL) |
| 152 | @@ -274,6 +290,7 @@ allocitem(const char *label, const char *output) |
| 153 | item->labellen = strlen(item->label); |
| 154 | item->next = NULL; |
| 155 | item->submenu = NULL; |
| 156 | + item->image = NULL; |
| 157 | |
| 158 | return item; |
| 159 | } |
| 160 | @@ -314,7 +331,7 @@ allocmenu(struct Menu *parent, struct Item *list, unsigned level) |
| 161 | |
| 162 | /* build the menu tree */ |
| 163 | static struct Menu * |
| 164 | -buildmenutree(unsigned level, const char *label, const char *output) |
| 165 | +buildmenutree(unsigned level, const char *label, const char *output, char *file) |
| 166 | { |
| 167 | static struct Menu *prevmenu = NULL; /* menu the previous item was added to */ |
| 168 | static struct Menu *rootmenu = NULL; /* menu to be returned */ |
| 169 | @@ -324,7 +341,7 @@ buildmenutree(unsigned level, const char *label, const char *output) |
| 170 | unsigned i; |
| 171 | |
| 172 | /* create the item */ |
| 173 | - curritem = allocitem(label, output); |
| 174 | + curritem = allocitem(label, output, file); |
| 175 | |
| 176 | /* put the item in the menu tree */ |
| 177 | if (prevmenu == NULL) { /* there is no menu yet */ |
| 178 | @@ -377,7 +394,7 @@ parsestdin(void) |
| 179 | { |
| 180 | struct Menu *rootmenu; |
| 181 | char *s, buf[BUFSIZ]; |
| 182 | - char *label, *output; |
| 183 | + char *file, *label, *output; |
| 184 | unsigned level = 0; |
| 185 | |
| 186 | rootmenu = NULL; |
| 187 | @@ -390,6 +407,13 @@ parsestdin(void) |
| 188 | s = level + buf; |
| 189 | label = strtok(s, "\t\n"); |
| 190 | |
| 191 | + /* get the filename */ |
| 192 | + file = NULL; |
| 193 | + if (label != NULL && strncmp(label, "IMG:", 4) == 0) { |
| 194 | + file = label + 4; |
| 195 | + label = strtok(NULL, "\t\n"); |
| 196 | + } |
| 197 | + |
| 198 | /* get the output */ |
| 199 | output = strtok(NULL, "\n"); |
| 200 | if (output == NULL) { |
| 201 | @@ -399,12 +423,36 @@ parsestdin(void) |
| 202 | output++; |
| 203 | } |
| 204 | |
| 205 | - rootmenu = buildmenutree(level, label, output); |
| 206 | + rootmenu = buildmenutree(level, label, output, file); |
| 207 | } |
| 208 | |
| 209 | return rootmenu; |
| 210 | } |
| 211 | |
| 212 | +/* load and scale image */ |
| 213 | +static Imlib_Image |
| 214 | +loadimage(const char *file, int size) |
| 215 | +{ |
| 216 | + Imlib_Image image; |
| 217 | + int width; |
| 218 | + int height; |
| 219 | + int imgsize; |
| 220 | + |
| 221 | + image = imlib_load_image(file); |
| 222 | + if (image == NULL) |
| 223 | + errx(1, "cannot load image %s", file); |
| 224 | + |
| 225 | + imlib_context_set_image(image); |
| 226 | + |
| 227 | + width = imlib_image_get_width(); |
| 228 | + height = imlib_image_get_height(); |
| 229 | + imgsize = MIN(width, height); |
| 230 | + |
| 231 | + image = imlib_create_cropped_scaled_image(0, 0, imgsize, imgsize, size, size); |
| 232 | + |
| 233 | + return image; |
| 234 | +} |
| 235 | + |
| 236 | /* recursivelly calculate menu geometry and set window hints */ |
| 237 | static void |
| 238 | calcmenu(struct Geometry *geom, struct Menu *menu) |
| 239 | @@ -430,8 +478,12 @@ calcmenu(struct Geometry *geom, struct Menu *menu) |
| 240 | |
| 241 | XftTextExtentsUtf8(dpy, dc.font, (XftChar8 *)item->label, |
| 242 | item->labellen, &ext); |
| 243 | - labelwidth = ext.xOff + dc.font->height * 2; |
| 244 | + labelwidth = ext.xOff + dc.font->height * 2 + imgpadding; |
| 245 | menu->w = MAX(menu->w, labelwidth); |
| 246 | + |
| 247 | + /* create image */ |
| 248 | + if (item->file != NULL) |
| 249 | + item->image = loadimage(item->file, dc.font->height); |
| 250 | } |
| 251 | |
| 252 | /* calculate menu's x and y positions */ |
| 253 | @@ -621,7 +673,7 @@ drawitem(struct Menu *menu, struct Item *item, XftColor *color) |
| 254 | { |
| 255 | int x, y; |
| 256 | |
| 257 | - x = dc.font->height; |
| 258 | + x = dc.font->height + imgpadding; |
| 259 | y = item->y + item->h/2 + dc.font->ascent/2 - 1; |
| 260 | XSetForeground(dpy, dc.gc, color[ColorFG].pixel); |
| 261 | XftDrawStringUtf8(menu->draw, &color[ColorFG], dc.font, |
| 262 | @@ -629,8 +681,8 @@ drawitem(struct Menu *menu, struct Item *item, XftColor *color) |
| 263 | |
| 264 | /* draw triangle, if item contains a submenu */ |
| 265 | if (item->submenu != NULL) { |
| 266 | - x = menu->w - dc.font->height/2 - triangle_width/2; |
| 267 | - y = item->y + item->h/2 - triangle_height/2 - 1; |
| 268 | + x = menu->w - (dc.font->height - triangle_width) / 2; |
| 269 | + y = item->y + (item->h - triangle_height) / 2; |
| 270 | |
| 271 | XPoint triangle[] = { |
| 272 | {x, y}, |
| 273 | @@ -642,6 +694,15 @@ drawitem(struct Menu *menu, struct Item *item, XftColor *color) |
| 274 | XFillPolygon(dpy, menu->pixmap, dc.gc, triangle, LEN(triangle), |
| 275 | Convex, CoordModeOrigin); |
| 276 | } |
| 277 | + |
| 278 | + /* draw image */ |
| 279 | + if (item->file != NULL) { |
| 280 | + x = imgpadding / 2; |
| 281 | + y = item->y + (item->h - dc.font->height) / 2; |
| 282 | + imlib_context_set_drawable(menu->pixmap); |
| 283 | + imlib_context_set_image(item->image); |
| 284 | + imlib_render_image_on_drawable(x, y); |
| 285 | + } |
| 286 | } |
| 287 | |
| 288 | /* draw items of the current menu and of its ancestors */ |
| 289 | @@ -831,6 +892,13 @@ freemenu(struct Menu *menu) |
| 290 | if (tmp->label != tmp->output) |
| 291 | free(tmp->label); |
| 292 | free(tmp->output); |
| 293 | + if (tmp->file != NULL) { |
| 294 | + free(tmp->file); |
| 295 | + if (item->image != NULL) { |
| 296 | + imlib_context_set_image(item->image); |
| 297 | + imlib_free_image(); |
| 298 | + } |
| 299 | + } |
| 300 | free(tmp); |
| 301 | } |
| 302 | |
| 303 | diff --git a/xmenu.sh b/xmenu.sh |
| 304 | index abd9a41..db08041 100755 |
| 305 | --- a/xmenu.sh |
| 306 | +++ b/xmenu.sh |
| 307 | @@ -2,7 +2,7 @@ |
| 308 | |
| 309 | cat <<EOF | xmenu | sh & |
| 310 | Applications |
| 311 | - Web Browser firefox |
| 312 | + IMG:./web.png Web Browser firefox |
| 313 | Image editor gimp |
| 314 | Terminal (xterm) xterm |
| 315 | Terminal (urxvt) urxvt |