A mirror of phillbush's xmenu.
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.
 
 
 
 
 

955 line
25 KiB

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