Fixed makefile
[xmenu] / xmenu.c
CommitLineData
a7732690 1#include <err.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <unistd.h>
6#include <X11/Xlib.h>
7#include <X11/Xutil.h>
f644b8bc 8#include <X11/Xresource.h>
858338d9 9#include <X11/XKBlib.h>
dbeb9940 10#include <X11/Xft/Xft.h>
858338d9 11
12#define ITEMPREV 0
13#define ITEMNEXT 1
a7732690 14
15/* macros */
16#define LEN(x) (sizeof (x) / sizeof (x[0]))
f1583285 17#define MAX(x,y) ((x)>(y)?(x):(y))
d8a7caf2 18#define MIN(x,y) ((x)<(y)?(x):(y))
a7732690 19
20/* color enum */
21enum {ColorFG, ColorBG, ColorLast};
22
23/* draw context structure */
24struct DC {
dbeb9940 25 XftColor normal[ColorLast];
26 XftColor selected[ColorLast];
27 XftColor decoration[ColorLast];
a7732690 28
29 Drawable d;
30 GC gc;
dbeb9940 31 XftFont *font;
a7732690 32};
33
34/* menu geometry structure */
35struct Geometry {
36 int itemb; /* item border */
37 int itemw; /* item width */
38 int itemh; /* item height */
a80fee22 39 int border; /* window border width */
40 int separator; /* menu separator width */
a7732690 41};
42
43/* screen geometry structure */
44struct ScreenGeometry {
45 int cursx, cursy; /* cursor position */
46 int screenw, screenh; /* screen width and height */
47};
48
49/* menu item structure */
50struct Item {
d8a7caf2 51 char *label; /* string to be drawed on menu */
52 char *output; /* string to be outputed when item is clicked */
53 int y; /* item y position relative to menu */
54 int h; /* item height */
55 size_t labellen; /* strlen(label) */
858338d9 56 struct Item *prev; /* previous item */
d8a7caf2 57 struct Item *next; /* next item */
58 struct Menu *submenu; /* submenu spawned by clicking on item */
a7732690 59};
60
61/* menu structure */
62struct Menu {
d8a7caf2 63 struct Menu *parent; /* parent menu */
64 struct Item *caller; /* item that spawned the menu */
65 struct Item *list; /* list of items contained by the menu */
66 struct Item *selected; /* item currently selected in the menu */
67 int x, y, w, h; /* menu geometry */
68 unsigned level; /* menu level relative to root */
69 Drawable pixmap; /* pixmap to draw the menu on */
dbeb9940 70 XftDraw *draw;
d8a7caf2 71 Window win; /* menu window to map on the screen */
a7732690 72};
73
74/* function declarations */
dbeb9940 75static void getcolor(const char *s, XftColor *color);
f644b8bc 76static void getresources(void);
a7732690 77static void setupdc(void);
78static void setupgeom(void);
79static void setupgrab(void);
a80fee22 80static struct Item *allocitem(const char *label, const char *output);
a7732690 81static struct Menu *allocmenu(struct Menu *parent, struct Item *list, unsigned level);
f15fc339 82static void getmenuitem(Window win, int y, struct Menu **menu_ret, struct Item **item_ret);
a7732690 83static void drawmenu(void);
84static void calcscreengeom(void);
85static void calcmenu(struct Menu *menu);
86static void setcurrmenu(struct Menu *currmenu_new);
87static void parsestdin(void);
88static void run(void);
f15fc339 89static void freewindow(struct Menu *menu);
08f16589 90static void cleanup(void);
a7732690 91static void usage(void);
92
93/* X variables */
94static Colormap colormap;
95static Display *dpy;
dbeb9940 96static Visual *visual;
a7732690 97static Window rootwin;
98static int screen;
99static struct DC dc;
100
101/* menu variables */
102static struct Menu *rootmenu = NULL;
103static struct Menu *currmenu = NULL;
104
105/* geometry variables */
106static struct Geometry geom;
a80fee22 107static struct ScreenGeometry screengeom;
a7732690 108
109/* flag variables */
110static Bool override_redirect = True;
111
112#include "config.h"
113
114int
115main(int argc, char *argv[])
116{
117 int ch;
118
119 while ((ch = getopt(argc, argv, "w")) != -1) {
120 switch (ch) {
121 case 'w':
122 override_redirect = False;
123 break;
124 default:
125 usage();
126 break;
127 }
128 }
129 argc -= optind;
130 argv += optind;
131
132 /* open connection to server and set X variables */
133 if ((dpy = XOpenDisplay(NULL)) == NULL)
134 errx(1, "cannot open display");
135 screen = DefaultScreen(dpy);
dbeb9940 136 visual = DefaultVisual(dpy, screen);
a7732690 137 rootwin = RootWindow(dpy, screen);
138 colormap = DefaultColormap(dpy, screen);
139
140 /* setup */
f644b8bc 141 getresources();
a7732690 142 setupdc();
143 setupgeom();
dbeb9940 144 if (override_redirect)
145 setupgrab();
a7732690 146
147 /* generate menus and recalculate them */
148 parsestdin();
149 if (rootmenu == NULL)
150 errx(1, "no menu generated");
151 calcscreengeom();
152 calcmenu(rootmenu);
153
d8a7caf2 154 /* map root menu */
155 currmenu = rootmenu;
156 XMapWindow(dpy, rootmenu->win);
157
a7732690 158 /* run event loop */
159 run();
160
08f16589 161 cleanup();
162
163 return 0;
a7732690 164}
165
f644b8bc 166/* read xrdb for configuration options */
167static void
168getresources(void)
169{
170 char *xrm;
171 long n;
172
173 XrmInitialize();
174 if ((xrm = XResourceManagerString(dpy))) {
175 char *type;
176 XrmDatabase xdb;
177 XrmValue xval;
178
179 xdb = XrmGetStringDatabase(xrm);
180
181 if (XrmGetResource(xdb, "xmenu.menuborder", "*", &type, &xval) == True)
182 if ((n = strtol(xval.addr, NULL, 10)) > 0)
183 menuborder = n;
184 if (XrmGetResource(xdb, "xmenu.separatorsize", "*", &type, &xval) == True)
185 if ((n = strtol(xval.addr, NULL, 10)) > 0)
186 separatorsize = n;
187 if (XrmGetResource(xdb, "xmenu.itemborder", "*", &type, &xval) == True)
188 if ((n = strtol(xval.addr, NULL, 10)) > 0)
189 itemborder = n;
190 if (XrmGetResource(xdb, "xmenu.width", "*", &type, &xval) == True)
191 if ((n = strtol(xval.addr, NULL, 10)) > 0)
192 width = n;
193 if (XrmGetResource(xdb, "xmenu.background", "*", &type, &xval) == True)
194 background = strdup(xval.addr);
195 if (XrmGetResource(xdb, "xmenu.foreground", "*", &type, &xval) == True)
196 foreground = strdup(xval.addr);
197 if (XrmGetResource(xdb, "xmenu.selbackground", "*", &type, &xval) == True)
198 selbackground = strdup(xval.addr);
199 if (XrmGetResource(xdb, "xmenu.selforeground", "*", &type, &xval) == True)
200 selforeground = strdup(xval.addr);
201 if (XrmGetResource(xdb, "xmenu.separator", "*", &type, &xval) == True)
202 separator = strdup(xval.addr);
203 if (XrmGetResource(xdb, "xmenu.border", "*", &type, &xval) == True)
204 border = strdup(xval.addr);
205 if (XrmGetResource(xdb, "xmenu.font", "*", &type, &xval) == True)
206 font = strdup(xval.addr);
207
208 XrmDestroyDatabase(xdb);
209 }
210}
211
a7732690 212/* get color from color string */
dbeb9940 213static void
214getcolor(const char *s, XftColor *color)
a7732690 215{
dbeb9940 216 if(!XftColorAllocName(dpy, visual, colormap, s, color))
a7732690 217 errx(1, "cannot allocate color: %s", s);
a7732690 218}
219
220/* init draw context */
221static void
222setupdc(void)
223{
224 /* get color pixels */
dbeb9940 225 getcolor(background, &dc.normal[ColorBG]);
226 getcolor(foreground, &dc.normal[ColorFG]);
227 getcolor(selbackground, &dc.selected[ColorBG]);
228 getcolor(selforeground, &dc.selected[ColorFG]);
229 getcolor(separator, &dc.decoration[ColorBG]);
230 getcolor(border, &dc.decoration[ColorFG]);
a7732690 231
232 /* try to get font */
dbeb9940 233 if ((dc.font = XftFontOpenName(dpy, screen, font)) == NULL)
a7732690 234 errx(1, "cannot load font");
a7732690 235
dbeb9940 236 /* create GC */
a7732690 237 dc.gc = XCreateGC(dpy, rootwin, 0, NULL);
a7732690 238}
239
240/* init menu geometry values */
241static void
242setupgeom(void)
243{
f644b8bc 244 geom.itemb = itemborder;
dbeb9940 245 geom.itemh = dc.font->height + itemborder * 2;
f644b8bc 246 geom.itemw = width;
247 geom.border = menuborder;
248 geom.separator = separatorsize;
a7732690 249}
250
251/* grab pointer */
252static void
253setupgrab(void)
254{
858338d9 255 if (XGrabPointer(dpy, rootwin, True, ButtonPressMask,
256 GrabModeAsync, GrabModeAsync, None,
257 None, CurrentTime) != GrabSuccess)
258 errx(1, "cannot grab pointer");
259 if (XGrabKeyboard(dpy, rootwin, True, GrabModeAsync,
260 GrabModeAsync, CurrentTime) != GrabSuccess)
261 errx(1, "cannot grab keyboard");
a7732690 262}
263
264/* allocate an item */
265static struct Item *
a80fee22 266allocitem(const char *label, const char *output)
a7732690 267{
268 struct Item *item;
269
270 if ((item = malloc(sizeof *item)) == NULL)
271 err(1, "malloc");
7fbd1c5e 272 if (*label == '\0') {
273 item->label = NULL;
274 item->output = NULL;
275 } else {
276 if ((item->label = strdup(label)) == NULL)
277 err(1, "strdup");
278 if ((item->output = strdup(output)) == NULL)
279 err(1, "strdup");
280 }
a80fee22 281 item->y = 0;
7fbd1c5e 282 item->h = item->label ? geom.itemh : geom.separator;
f1583285 283 if (item->label == NULL)
284 item->labellen = 0;
285 else
286 item->labellen = strlen(item->label);
a7732690 287 item->next = NULL;
288 item->submenu = NULL;
289
290 return item;
291}
292
293/* allocate a menu */
294static struct Menu *
295allocmenu(struct Menu *parent, struct Item *list, unsigned level)
296{
297 XSetWindowAttributes swa;
298 struct Menu *menu;
299
300 if ((menu = malloc(sizeof *menu)) == NULL)
301 err(1, "malloc");
302 menu->parent = parent;
303 menu->list = list;
d888f2ca 304 menu->caller = NULL;
a7732690 305 menu->selected = NULL;
a7732690 306 menu->w = geom.itemw;
a80fee22 307 menu->h = 0; /* calculated by calcmenu() */
308 menu->x = 0; /* calculated by calcmenu() */
309 menu->y = 0; /* calculated by calcmenu() */
a7732690 310 menu->level = level;
a7732690 311
312 swa.override_redirect = override_redirect;
dbeb9940 313 swa.background_pixel = dc.decoration[ColorBG].pixel;
314 swa.border_pixel = dc.decoration[ColorFG].pixel;
a7732690 315 swa.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask
f15fc339 316 | PointerMotionMask | LeaveWindowMask;
a7732690 317 menu->win = XCreateWindow(dpy, rootwin, 0, 0, geom.itemw, geom.itemh, geom.border,
318 CopyFromParent, CopyFromParent, CopyFromParent,
319 CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
320 &swa);
321
322 return menu;
323}
324
325/* create menus and items from the stdin */
326static void
327parsestdin(void)
328{
329 char *s, buf[BUFSIZ];
330 char *label, *output;
331 unsigned level = 0;
332 unsigned i;
a80fee22 333 struct Item *curritem = NULL; /* item currently being read */
334 struct Menu *prevmenu = NULL; /* menu the previous item was added to */
335 struct Item *item; /* dummy item for for loops */
336 struct Menu *menu; /* dummy menu for for loops */
a7732690 337 size_t count = 0; /* number of items in the current menu */
338
339 while (fgets(buf, BUFSIZ, stdin) != NULL) {
340 level = 0;
341 s = buf;
342
343 while (*s == '\t') {
344 level++;
345 s++;
346 }
347
348 label = output = s;
349
350 while (*s != '\0' && *s != '\t' && *s != '\n')
351 s++;
352
353 while (*s == '\t')
354 *s++ = '\0';
355
356 if (*s != '\0' && *s != '\n')
357 output = s;
358
359 while (*s != '\0' && *s != '\n')
360 s++;
361
362 if (*s == '\n')
363 *s = '\0';
364
a80fee22 365 curritem = allocitem(label, output);
a7732690 366
367 if (prevmenu == NULL) { /* there is no menu yet */
a80fee22 368 menu = allocmenu(NULL, curritem, level);
a7732690 369 rootmenu = menu;
370 prevmenu = menu;
371 count = 1;
858338d9 372 curritem->prev = NULL;
373 curritem->next = NULL;
a7732690 374 } else if (level < prevmenu->level) { /* item is continuation of a parent menu*/
375 for (menu = prevmenu, i = level;
376 menu != NULL && i < prevmenu->level;
377 menu = menu->parent, i++)
378 ;
379
380 if (menu == NULL)
381 errx(1, "reached NULL menu");
382
a80fee22 383 for (item = menu->list; item->next != NULL; item = item->next)
a7732690 384 ;
385
a80fee22 386 item->next = curritem;
858338d9 387
388 curritem->prev = item;
389 curritem->next = NULL;
390
a7732690 391 prevmenu = menu;
392 } else if (level == prevmenu->level) { /* item is a continuation of current menu */
a80fee22 393 for (item = prevmenu->list; item->next != NULL; item = item->next)
a7732690 394 ;
a80fee22 395 item->next = curritem;
858338d9 396
397 curritem->prev = item;
398 curritem->next = NULL;
399
a7732690 400 } else if (level > prevmenu->level) { /* item begins a new menu */
a80fee22 401 menu = allocmenu(prevmenu, curritem, level);
a7732690 402
a80fee22 403 for (item = prevmenu->list; item->next != NULL; item = item->next)
a7732690 404 ;
405
a80fee22 406 item->submenu = menu;
d888f2ca 407 menu->caller = item;
a7732690 408
858338d9 409 curritem->prev = NULL;
410 curritem->next = NULL;
411
a7732690 412 prevmenu = menu;
413 }
a80fee22 414 count++;
a7732690 415 }
416}
417
418/* calculate screen geometry */
419static void
420calcscreengeom(void)
421{
422 Window w1, w2; /* unused variables */
423 int a, b; /* unused variables */
424 unsigned mask; /* unused variable */
425
a80fee22 426 XQueryPointer(dpy, rootwin, &w1, &w2, &screengeom.cursx, &screengeom.cursy, &a, &b, &mask);
427 screengeom.screenw = DisplayWidth(dpy, screen);
428 screengeom.screenh = DisplayHeight(dpy, screen);
a7732690 429}
430
431/* recursivelly calculate height and position of the menus */
432static void
433calcmenu(struct Menu *menu)
434{
435 XWindowChanges changes;
09c13122 436 XSizeHints sizeh;
dbeb9940 437 XGlyphInfo ext;
a80fee22 438 struct Item *item;
f1583285 439 int labelwidth;
a7732690 440
f1583285 441 /* calculate items positions and menu width and height */
442 menu->w = geom.itemw;
a80fee22 443 for (item = menu->list; item != NULL; item = item->next) {
444 item->y = menu->h;
7fbd1c5e 445 if (item->label == NULL) /* height for separator item */
a80fee22 446 menu->h += geom.separator;
447 else
448 menu->h += geom.itemh;
f1583285 449
dbeb9940 450 XftTextExtentsUtf8(dpy, dc.font, (XftChar8 *)item->label,
451 item->labellen, &ext);
452 labelwidth = ext.xOff + dc.font->height * 2;
f1583285 453 menu->w = MAX(menu->w, labelwidth);
a80fee22 454 }
a7732690 455
456 /* calculate menu's x and y positions */
457 if (menu->parent == NULL) { /* if root menu, calculate in respect to cursor */
a80fee22 458 if (screengeom.screenw - screengeom.cursx >= menu->w)
459 menu->x = screengeom.cursx;
460 else if (screengeom.cursx > menu->w)
461 menu->x = screengeom.cursx - menu->w;
462
463 if (screengeom.screenh - screengeom.cursy >= menu->h)
464 menu->y = screengeom.cursy;
465 else if (screengeom.screenh > menu->h)
466 menu->y = screengeom.screenh - menu->h;
a7732690 467 } else { /* else, calculate in respect to parent menu */
468
469 /* search for the item in parent menu that generates this menu */
a80fee22 470 for (item = menu->parent->list; item->submenu != menu; item = item->next)
a7732690 471 ;
472
f644b8bc 473 if (screengeom.screenw - (menu->parent->x + menu->parent->w + geom.border) >= menu->w)
474 menu->x = menu->parent->x + menu->parent->w + geom.border;
475 else if (menu->parent->x > menu->w + geom.border)
476 menu->x = menu->parent->x - menu->w - geom.border;
a7732690 477
a80fee22 478 if (screengeom.screenh - (item->y + menu->parent->y) > menu->h)
479 menu->y = item->y + menu->parent->y;
480 else if (screengeom.screenh - menu->parent->y > menu->h)
a7732690 481 menu->y = menu->parent->y;
a80fee22 482 else if (screengeom.screenh > menu->h)
483 menu->y = screengeom.screenh - menu->h;
a7732690 484 }
485
486 /* update menu geometry */
487 changes.height = menu->h;
f1583285 488 changes.width = menu->w;
a7732690 489 changes.x = menu->x;
490 changes.y = menu->y;
f1583285 491 XConfigureWindow(dpy, menu->win, CWWidth | CWHeight | CWX | CWY, &changes);
a7732690 492
09c13122 493 /* set window manager size hints */
494 sizeh.flags = PMaxSize | PMinSize;
495 sizeh.min_width = sizeh.max_width = menu->w;
496 sizeh.min_height = sizeh.max_height = menu->h;
497 XSetWMNormalHints(dpy, menu->win, &sizeh);
498
dbeb9940 499 /* create pixmap and XftDraw */
f15fc339 500 menu->pixmap = XCreatePixmap(dpy, menu->win, menu->w, menu->h,
501 DefaultDepth(dpy, screen));
dbeb9940 502 menu->draw = XftDrawCreate(dpy, menu->pixmap, visual, colormap);
f15fc339 503
a80fee22 504 /* calculate positions of submenus */
a7732690 505 for (item = menu->list; item != NULL; item = item->next) {
506 if (item->submenu != NULL)
507 calcmenu(item->submenu);
508 }
509}
510
511/* get menu and item of given window and position */
512static void
a80fee22 513getmenuitem(Window win, int y,
a7732690 514 struct Menu **menu_ret, struct Item **item_ret)
515{
516 struct Menu *menu = NULL;
517 struct Item *item = NULL;
518
519 for (menu = currmenu; menu != NULL; menu = menu->parent) {
520 if (menu->win == win) {
521 for (item = menu->list; item != NULL; item = item->next) {
7fbd1c5e 522 if (y >= item->y && y <= item->y + item->h) {
a7732690 523 goto done;
524 }
525 }
526 }
527 }
528
529
530done:
531 *menu_ret = menu;
532 *item_ret = item;
533}
534
535/* set currentmenu to menu, umap previous menus and map current menu and its parents */
536static void
537setcurrmenu(struct Menu *currmenu_new)
538{
d8a7caf2 539 struct Menu *menu, *menu_;
d888f2ca 540 struct Item *item;
d8a7caf2 541 struct Menu *lcamenu; /* lowest common ancestor menu */
542 unsigned minlevel; /* level of the closest to root menu */
543 unsigned maxlevel; /* level of the closest to root menu */
a7732690 544
545 if (currmenu_new == currmenu)
546 return;
547
d8a7caf2 548 /* find lowest common ancestor menu */
549 lcamenu = rootmenu;
550 if (currmenu != NULL) {
551 minlevel = MIN(currmenu_new->level, currmenu->level);
552 maxlevel = MAX(currmenu_new->level, currmenu->level);
553 if (currmenu_new->level == maxlevel) {
554 menu = currmenu_new;
555 menu_ = currmenu;
556 } else {
557 menu = currmenu;
558 menu_ = currmenu_new;
559 }
560 while (menu->level > minlevel)
561 menu = menu->parent;
562
563 while (menu != menu_) {
564 menu = menu->parent;
565 menu_ = menu_->parent;
566 }
567 lcamenu = menu;
568 }
569
873c080c 570 /* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
d8a7caf2 571 for (menu = currmenu; menu != lcamenu; menu = menu->parent) {
a7732690 572 XUnmapWindow(dpy, menu->win);
573 }
574
575 currmenu = currmenu_new;
576
873c080c 577 /* map menus from currmenu (inclusive) until lcamenu (exclusive) */
d888f2ca 578 item = NULL;
d8a7caf2 579 for (menu = currmenu; menu != lcamenu; menu = menu->parent) {
a7732690 580 XMapWindow(dpy, menu->win);
d888f2ca 581 if (item != NULL)
582 menu->selected = item;
583 item = menu->caller;
584 }
a7732690 585}
586
587/* draw items of the current menu and of its ancestors */
588static void
589drawmenu(void)
590{
591 struct Menu *menu;
592 struct Item *item;
593
594 for (menu = currmenu; menu != NULL; menu = menu->parent) {
a7732690 595 for (item = menu->list; item != NULL; item = item->next) {
dbeb9940 596 XftColor *color;
a7732690 597 int labelx, labely;
a7732690 598
599 /* determine item color */
f644b8bc 600 if (item == menu->selected)
601 color = dc.selected;
a7732690 602 else
f644b8bc 603 color = dc.normal;
604
605 /* continue if item is a separator */
606 if (item->label == NULL)
607 continue;
a7732690 608
a7732690 609 /* draw item box */
dbeb9940 610 XSetForeground(dpy, dc.gc, color[ColorBG].pixel);
d888f2ca 611 XDrawRectangle(dpy, menu->pixmap, dc.gc, 0, item->y,
612 menu->w, item->h);
f15fc339 613 XFillRectangle(dpy, menu->pixmap, dc.gc, 0, item->y,
f1583285 614 menu->w, item->h);
7fbd1c5e 615
a7732690 616 /* draw item label */
dbeb9940 617 labelx = 0 + dc.font->height;
618 labely = item->y + dc.font->height + geom.itemb / 2;
619 XSetForeground(dpy, dc.gc, color[ColorFG].pixel);
620 XftDrawStringUtf8(menu->draw, &color[ColorFG], dc.font,
621 labelx, labely, item->label,
622 item->labellen);
a7732690 623
624 /* draw triangle, if item contains a submenu */
625 if (item->submenu != NULL) {
dbeb9940 626 int trianglex = menu->w - dc.font->height + geom.itemb - 1;
2b6968f9 627 int triangley = item->y + (3 * item->h)/8 -1;
a7732690 628
629 XPoint triangle[] = {
630 {trianglex, triangley},
2b6968f9 631 {trianglex + item->h/8 + 1, item->y + item->h/2},
632 {trianglex, triangley + item->h/4 + 2},
a7732690 633 {trianglex, triangley}
634 };
635
f15fc339 636 XFillPolygon(dpy, menu->pixmap, dc.gc, triangle, LEN(triangle),
a7732690 637 Convex, CoordModeOrigin);
638 }
f15fc339 639
640 XCopyArea(dpy, menu->pixmap, menu->win, dc.gc, 0, item->y,
641 menu->w, item->h, 0, item->y);
a7732690 642 }
643 }
644}
645
858338d9 646/* cycle through the items; non-zero direction is next, zero is prev */
647static struct Item *
648itemcycle(int direction)
649{
650 struct Item *item;
651 struct Item *lastitem;
652
653 item = NULL;
654
655 if (direction == ITEMNEXT) {
656 if (currmenu->selected == NULL)
657 item = currmenu->list;
658 else if (currmenu->selected->next != NULL)
659 item = currmenu->selected->next;
660
661 while (item != NULL && item->label == NULL)
662 item = item->next;
663
664 if (item == NULL)
665 item = currmenu->list;
666 } else {
667 for (lastitem = currmenu->list;
668 lastitem != NULL && lastitem->next != NULL;
669 lastitem = lastitem->next)
670 ;
671
672 if (currmenu->selected == NULL)
673 item = lastitem;
674 else if (currmenu->selected->prev != NULL)
675 item = currmenu->selected->prev;
676
677 while (item != NULL && item->label == NULL)
678 item = item->prev;
679
680 if (item == NULL)
681 item = lastitem;
682 }
683
684 return item;
685}
686
a7732690 687/* run event loop */
688static void
689run(void)
690{
691 struct Menu *menu;
692 struct Item *item;
693 struct Item *previtem = NULL;
858338d9 694 KeySym ksym;
a7732690 695 XEvent ev;
696
a7732690 697 while (!XNextEvent(dpy, &ev)) {
698 switch(ev.type) {
699 case Expose:
858338d9 700 if (ev.xexpose.count == 0)
701 drawmenu();
a7732690 702 break;
703 case MotionNotify:
a80fee22 704 getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
a7732690 705 if (menu != NULL && item != NULL) {
706 if (previtem != item) {
707 if (item->submenu != NULL)
708 setcurrmenu(item->submenu);
709 else
710 setcurrmenu(menu);
711 previtem = item;
d888f2ca 712 drawmenu();
713 } else if (menu->selected != item) {
a7732690 714 menu->selected = item;
d888f2ca 715 drawmenu();
716 }
a7732690 717 }
a7732690 718 break;
719 case ButtonRelease:
a80fee22 720 getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
a7732690 721 if (menu != NULL && item != NULL) {
858338d9 722selectitem:
7fbd1c5e 723 if (item->label == NULL)
724 break; /* ignore separators */
a7732690 725 if (item->submenu != NULL) {
726 setcurrmenu(item->submenu);
727 } else {
728 printf("%s\n", item->output);
08f16589 729 return;
a7732690 730 }
858338d9 731 currmenu->selected = currmenu->list;
a7732690 732 drawmenu();
858338d9 733 break;
a7732690 734 } else {
08f16589 735 return;
a7732690 736 }
858338d9 737 case ButtonPress:
738 getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item);
739 if (menu == NULL || item == NULL)
740 return;
741 break;
742 case KeyPress:
743 ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0);
744
745 if (ksym == XK_Escape && currmenu == rootmenu)
746 return;
747
748 /* Shift-Tab = ISO_Left_Tab */
749 if (ksym == XK_Tab && (ev.xkey.state & ShiftMask))
750 ksym = XK_ISO_Left_Tab;
751
752 /* cycle through menu */
753 item = NULL;
754 if (ksym == XK_ISO_Left_Tab || ksym == XK_Up) {
755 item = itemcycle(ITEMPREV);
756 } else if (ksym == XK_Tab || ksym == XK_Down) {
757 item = itemcycle(ITEMNEXT);
758 } else if ((ksym == XK_Return || ksym == XK_Right) &&
759 currmenu->selected != NULL) {
760 item = currmenu->selected;
761 goto selectitem;
762 } else if ((ksym == XK_Escape || ksym == XK_Left) &&
763 currmenu->parent != NULL) {
764 item = currmenu->parent->selected;
765 setcurrmenu(currmenu->parent);
766 } else
767 break;
768 currmenu->selected = item;
769 drawmenu();
a7732690 770 break;
f15fc339 771 case LeaveNotify:
772 currmenu->selected = NULL;
773 drawmenu();
774 break;
a7732690 775 }
776 }
777}
778
f15fc339 779/* recursivelly free a pixmap */
780static void
781freewindow(struct Menu *menu)
782{
783 struct Item *item;
784
785 for (item = menu->list; item != NULL; item = item->next)
786 if (item->submenu != NULL)
787 freewindow(item->submenu);
788
789 XFreePixmap(dpy, menu->pixmap);
dbeb9940 790 XftDrawDestroy(menu->draw);
f15fc339 791 XDestroyWindow(dpy, menu->win);
792}
793
a7732690 794/* cleanup and exit */
795static void
08f16589 796cleanup(void)
a7732690 797{
f15fc339 798 freewindow(rootmenu);
dbeb9940 799
800 XftColorFree(dpy, visual, colormap, &dc.normal[ColorBG]);
801 XftColorFree(dpy, visual, colormap, &dc.normal[ColorFG]);
802 XftColorFree(dpy, visual, colormap, &dc.selected[ColorBG]);
803 XftColorFree(dpy, visual, colormap, &dc.selected[ColorFG]);
804 XftColorFree(dpy, visual, colormap, &dc.decoration[ColorBG]);
805 XftColorFree(dpy, visual, colormap, &dc.decoration[ColorFG]);
806
f15fc339 807 XFreeGC(dpy, dc.gc);
a7732690 808 XCloseDisplay(dpy);
a7732690 809}
810
811/* show usage */
812static void
813usage(void)
814{
08f16589 815 (void)fprintf(stderr, "usage: xmenu [-w]\n");
a7732690 816 exit(1);
817}