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