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.
 
 
 
 
 

1536 lines
40 KiB

  1. #include <ctype.h>
  2. #include <err.h>
  3. #include <errno.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <limits.h>
  8. #include <locale.h>
  9. #include <time.h>
  10. #include <unistd.h>
  11. #include <X11/Xlib.h>
  12. #include <X11/Xatom.h>
  13. #include <X11/Xutil.h>
  14. #include <X11/Xresource.h>
  15. #include <X11/XKBlib.h>
  16. #include <X11/Xft/Xft.h>
  17. #include <X11/extensions/Xinerama.h>
  18. #include <Imlib2.h>
  19. #include "xmenu.h"
  20. /* X stuff */
  21. static Display *dpy;
  22. static int screen;
  23. static Visual *visual;
  24. static Window rootwin;
  25. static Colormap colormap;
  26. static XrmDatabase xdb;
  27. static char *xrm;
  28. static struct DC dc;
  29. static struct Monitor mon;
  30. static Atom utf8string;
  31. static Atom wmdelete;
  32. static Atom netatom[NetLast];
  33. static XIM xim;
  34. /* flags */
  35. static int iflag = 0; /* whether to disable icons */
  36. static int rflag = 0; /* whether to disable right-click */
  37. static int mflag = 0; /* whether the user specified a monitor with -p */
  38. static int pflag = 0; /* whether the user specified a position with -p */
  39. static int wflag = 0; /* whether to let the window manager control XMenu */
  40. /* include config variable */
  41. #include "config.h"
  42. /* show usage */
  43. static void
  44. usage(void)
  45. {
  46. (void)fprintf(stderr, "usage: xmenu [-irw] [-p position] [title]\n");
  47. exit(1);
  48. }
  49. /* parse position string from -p,
  50. * put results on config.posx, config.posy, and config.monitor */
  51. static void
  52. parseposition(char *optarg)
  53. {
  54. long n;
  55. char *s = optarg;
  56. char *endp;
  57. n = strtol(s, &endp, 10);
  58. if (errno == ERANGE || n > INT_MAX || n < 0 || endp == s || *endp != 'x')
  59. goto error;
  60. config.posx = n;
  61. s = endp+1;
  62. n = strtol(s, &endp, 10);
  63. if (errno == ERANGE || n > INT_MAX || n < 0 || endp == s)
  64. goto error;
  65. config.posy = n;
  66. if (*endp == ':') {
  67. s = endp+1;
  68. mflag = 1;
  69. if (strncasecmp(s, "CUR", 3) == 0) {
  70. config.monitor = -1;
  71. endp = s+3;
  72. } else {
  73. n = strtol(s, &endp, 10);
  74. if (errno == ERANGE || n > INT_MAX || n < 0 || endp == s || *endp != '\0')
  75. goto error;
  76. config.monitor = n;
  77. }
  78. } else if (*endp != '\0') {
  79. goto error;
  80. }
  81. return;
  82. error:
  83. errx(1, "improper position: %s", optarg);
  84. }
  85. /* get configuration from X resources */
  86. static void
  87. getresources(void)
  88. {
  89. char *type;
  90. XrmValue xval;
  91. if (xrm == NULL || xdb == NULL)
  92. return;
  93. if (XrmGetResource(xdb, "xmenu.borderWidth", "*", &type, &xval) == True)
  94. GETNUM(config.border_pixels, xval.addr)
  95. if (XrmGetResource(xdb, "xmenu.separatorWidth", "*", &type, &xval) == True)
  96. GETNUM(config.separator_pixels, xval.addr)
  97. if (XrmGetResource(xdb, "xmenu.height", "*", &type, &xval) == True)
  98. GETNUM(config.height_pixels, xval.addr)
  99. if (XrmGetResource(xdb, "xmenu.width", "*", &type, &xval) == True)
  100. GETNUM(config.width_pixels, xval.addr)
  101. if (XrmGetResource(xdb, "xmenu.gap", "*", &type, &xval) == True)
  102. GETNUM(config.gap_pixels, xval.addr)
  103. if (XrmGetResource(xdb, "xmenu.background", "*", &type, &xval) == True)
  104. config.background_color = xval.addr;
  105. if (XrmGetResource(xdb, "xmenu.foreground", "*", &type, &xval) == True)
  106. config.foreground_color = xval.addr;
  107. if (XrmGetResource(xdb, "xmenu.selbackground", "*", &type, &xval) == True)
  108. config.selbackground_color = xval.addr;
  109. if (XrmGetResource(xdb, "xmenu.selforeground", "*", &type, &xval) == True)
  110. config.selforeground_color = xval.addr;
  111. if (XrmGetResource(xdb, "xmenu.separator", "*", &type, &xval) == True)
  112. config.separator_color = xval.addr;
  113. if (XrmGetResource(xdb, "xmenu.border", "*", &type, &xval) == True)
  114. config.border_color = xval.addr;
  115. if (XrmGetResource(xdb, "xmenu.font", "*", &type, &xval) == True)
  116. config.font = xval.addr;
  117. if (XrmGetResource(xdb, "xmenu.alignment", "*", &type, &xval) == True) {
  118. if (strcasecmp(xval.addr, "center") == 0)
  119. config.alignment = CenterAlignment;
  120. else if (strcasecmp(xval.addr, "left") == 0)
  121. config.alignment = LeftAlignment;
  122. else if (strcasecmp(xval.addr, "right") == 0)
  123. config.alignment = RightAlignment;
  124. }
  125. }
  126. /* get configuration from command-line options */
  127. static char *
  128. getoptions(int argc, char *argv[])
  129. {
  130. int ch;
  131. while ((ch = getopt(argc, argv, "ip:rw")) != -1) {
  132. switch (ch) {
  133. case 'i':
  134. iflag = 1;
  135. break;
  136. case 'p':
  137. pflag = 1;
  138. parseposition(optarg);
  139. break;
  140. case 'r':
  141. rflag = 1;
  142. break;
  143. case 'w':
  144. wflag = 1;
  145. break;
  146. default:
  147. usage();
  148. break;
  149. }
  150. }
  151. argc -= optind;
  152. argv += optind;
  153. if (argc > 1)
  154. usage();
  155. else if (argc == 1)
  156. return *argv;
  157. return PROGNAME;
  158. }
  159. /* parse font string */
  160. static void
  161. parsefonts(const char *s)
  162. {
  163. const char *p;
  164. char buf[1024];
  165. size_t nfont = 0;
  166. dc.nfonts = 1;
  167. for (p = s; *p; p++)
  168. if (*p == ',')
  169. dc.nfonts++;
  170. if ((dc.fonts = calloc(dc.nfonts, sizeof *dc.fonts)) == NULL)
  171. err(1, "calloc");
  172. p = s;
  173. while (*p != '\0') {
  174. size_t i;
  175. i = 0;
  176. while (isspace(*p))
  177. p++;
  178. while (i < sizeof buf && *p != '\0' && *p != ',')
  179. buf[i++] = *p++;
  180. if (i >= sizeof buf)
  181. errx(1, "font name too long");
  182. if (*p == ',')
  183. p++;
  184. buf[i] = '\0';
  185. if (nfont == 0)
  186. if ((dc.pattern = FcNameParse((FcChar8 *)buf)) == NULL)
  187. errx(1, "the first font in the cache must be loaded from a font string");
  188. if ((dc.fonts[nfont++] = XftFontOpenName(dpy, screen, buf)) == NULL)
  189. errx(1, "could not load font");
  190. }
  191. }
  192. /* get color from color string */
  193. static void
  194. ealloccolor(const char *s, XftColor *color)
  195. {
  196. if(!XftColorAllocName(dpy, visual, colormap, s, color))
  197. errx(1, "could not allocate color: %s", s);
  198. }
  199. /* query monitor information and cursor position */
  200. static void
  201. initmonitor(void)
  202. {
  203. XineramaScreenInfo *info = NULL;
  204. Window dw; /* dummy variable */
  205. int di; /* dummy variable */
  206. unsigned du; /* dummy variable */
  207. int cursx, cursy; /* cursor position */
  208. int nmons;
  209. int i;
  210. XQueryPointer(dpy, rootwin, &dw, &dw, &cursx, &cursy, &di, &di, &du);
  211. mon.x = mon.y = 0;
  212. mon.w = DisplayWidth(dpy, screen);
  213. mon.h = DisplayHeight(dpy, screen);
  214. if ((info = XineramaQueryScreens(dpy, &nmons)) != NULL) {
  215. int selmon = 0;
  216. if (!mflag || config.monitor < 0 || config.monitor >= nmons) {
  217. for (i = 0; i < nmons; i++) {
  218. if (BETWEEN(cursx, info[i].x_org, info[i].x_org + info[i].width) &&
  219. BETWEEN(cursy, info[i].y_org, info[i].y_org + info[i].height)) {
  220. selmon = i;
  221. break;
  222. }
  223. }
  224. } else {
  225. selmon = config.monitor;
  226. }
  227. mon.x = info[selmon].x_org;
  228. mon.y = info[selmon].y_org;
  229. mon.w = info[selmon].width;
  230. mon.h = info[selmon].height;
  231. XFree(info);
  232. }
  233. if (!pflag) {
  234. config.posx = cursx;
  235. config.posy = cursy;
  236. } else if (mflag) {
  237. config.posx += mon.x;
  238. config.posy += mon.y;
  239. }
  240. }
  241. /* init draw context */
  242. static void
  243. initdc(void)
  244. {
  245. /* get color pixels */
  246. ealloccolor(config.background_color, &dc.normal[ColorBG]);
  247. ealloccolor(config.foreground_color, &dc.normal[ColorFG]);
  248. ealloccolor(config.selbackground_color, &dc.selected[ColorBG]);
  249. ealloccolor(config.selforeground_color, &dc.selected[ColorFG]);
  250. ealloccolor(config.separator_color, &dc.separator);
  251. ealloccolor(config.border_color, &dc.border);
  252. /* parse fonts */
  253. parsefonts(config.font);
  254. /* create common GC */
  255. dc.gc = XCreateGC(dpy, rootwin, 0, NULL);
  256. }
  257. /* calculate icon size */
  258. static void
  259. initiconsize(void)
  260. {
  261. config.iconsize = config.height_pixels - config.iconpadding * 2;
  262. }
  263. /* intern atoms */
  264. static void
  265. initatoms(void)
  266. {
  267. utf8string = XInternAtom(dpy, "UTF8_STRING", False);
  268. wmdelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
  269. netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
  270. netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
  271. netatom[NetWMWindowTypePopupMenu] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False);
  272. }
  273. /* allocate an item */
  274. static struct Item *
  275. allocitem(const char *label, const char *output, char *file)
  276. {
  277. struct Item *item;
  278. if ((item = malloc(sizeof *item)) == NULL)
  279. err(1, "malloc");
  280. if (label == NULL) {
  281. item->label = NULL;
  282. item->output = NULL;
  283. } else {
  284. if ((item->label = strdup(label)) == NULL)
  285. err(1, "strdup");
  286. if (label == output) {
  287. item->output = item->label;
  288. } else {
  289. if ((item->output = strdup(output)) == NULL)
  290. err(1, "strdup");
  291. }
  292. }
  293. if (file == NULL) {
  294. item->file = NULL;
  295. } else {
  296. if ((item->file = strdup(file)) == NULL)
  297. err(1, "strdup");
  298. }
  299. item->y = 0;
  300. item->h = 0;
  301. item->next = NULL;
  302. item->submenu = NULL;
  303. item->icon = NULL;
  304. return item;
  305. }
  306. /* allocate a menu and create its window */
  307. static struct Menu *
  308. allocmenu(struct Menu *parent, struct Item *list, unsigned level)
  309. {
  310. XSetWindowAttributes swa;
  311. struct Menu *menu;
  312. if ((menu = malloc(sizeof *menu)) == NULL)
  313. err(1, "malloc");
  314. menu->parent = parent;
  315. menu->list = list;
  316. menu->caller = NULL;
  317. menu->selected = NULL;
  318. menu->w = 0; /* recalculated by setupmenu() */
  319. menu->h = 0; /* recalculated by setupmenu() */
  320. menu->x = mon.x; /* recalculated by setupmenu() */
  321. menu->y = mon.y; /* recalculated by setupmenu() */
  322. menu->level = level;
  323. menu->drawn = 0;
  324. menu->hasicon = 0;
  325. swa.override_redirect = (wflag) ? False : True;
  326. swa.background_pixel = dc.normal[ColorBG].pixel;
  327. swa.border_pixel = dc.border.pixel;
  328. swa.save_under = True; /* pop-up windows should save_under*/
  329. swa.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask
  330. | PointerMotionMask | LeaveWindowMask;
  331. if (wflag)
  332. swa.event_mask |= StructureNotifyMask;
  333. menu->win = XCreateWindow(dpy, rootwin, 0, 0, 1, 1, 0,
  334. CopyFromParent, CopyFromParent, CopyFromParent,
  335. CWOverrideRedirect | CWBackPixel |
  336. CWBorderPixel | CWEventMask | CWSaveUnder,
  337. &swa);
  338. menu->xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
  339. XNClientWindow, menu->win, XNFocusWindow, menu->win, NULL);
  340. if (menu->xic == NULL)
  341. errx(1, "XCreateIC: could not obtain input method");
  342. return menu;
  343. }
  344. /* build the menu tree */
  345. static struct Menu *
  346. buildmenutree(unsigned level, const char *label, const char *output, char *file)
  347. {
  348. static struct Menu *prevmenu = NULL; /* menu the previous item was added to */
  349. static struct Menu *rootmenu = NULL; /* menu to be returned */
  350. struct Item *curritem = NULL; /* item currently being read */
  351. struct Item *item; /* dummy item for loops */
  352. struct Menu *menu; /* dummy menu for loops */
  353. unsigned i;
  354. /* create the item */
  355. curritem = allocitem(label, output, file);
  356. /* put the item in the menu tree */
  357. if (prevmenu == NULL) { /* there is no menu yet */
  358. menu = allocmenu(NULL, curritem, level);
  359. rootmenu = menu;
  360. prevmenu = menu;
  361. curritem->prev = NULL;
  362. } else if (level < prevmenu->level) { /* item is continuation of a parent menu */
  363. /* go up the menu tree until find the menu this item continues */
  364. for (menu = prevmenu, i = level;
  365. menu != NULL && i != prevmenu->level;
  366. menu = menu->parent, i++)
  367. ;
  368. if (menu == NULL)
  369. errx(1, "improper indentation detected");
  370. /* find last item in the new menu */
  371. for (item = menu->list; item->next != NULL; item = item->next)
  372. ;
  373. prevmenu = menu;
  374. item->next = curritem;
  375. curritem->prev = item;
  376. } else if (level == prevmenu->level) { /* item is a continuation of current menu */
  377. /* find last item in the previous menu */
  378. for (item = prevmenu->list; item->next != NULL; item = item->next)
  379. ;
  380. item->next = curritem;
  381. curritem->prev = item;
  382. } else if (level > prevmenu->level) { /* item begins a new menu */
  383. menu = allocmenu(prevmenu, curritem, level);
  384. /* find last item in the previous menu */
  385. for (item = prevmenu->list; item->next != NULL; item = item->next)
  386. ;
  387. prevmenu = menu;
  388. menu->caller = item;
  389. item->submenu = menu;
  390. curritem->prev = NULL;
  391. }
  392. if (curritem->file)
  393. prevmenu->hasicon = 1;
  394. return rootmenu;
  395. }
  396. /* create menus and items from the stdin */
  397. static struct Menu *
  398. parsestdin(void)
  399. {
  400. struct Menu *rootmenu;
  401. char *s, buf[BUFSIZ];
  402. char *file, *label, *output;
  403. unsigned level = 0;
  404. rootmenu = NULL;
  405. while (fgets(buf, BUFSIZ, stdin) != NULL) {
  406. /* get the indentation level */
  407. level = strspn(buf, "\t");
  408. /* get the label */
  409. s = level + buf;
  410. label = strtok(s, "\t\n");
  411. /* get the filename */
  412. file = NULL;
  413. if (label != NULL && strncmp(label, "IMG:", 4) == 0) {
  414. file = label + 4;
  415. label = strtok(NULL, "\t\n");
  416. }
  417. /* get the output */
  418. output = strtok(NULL, "\n");
  419. if (output == NULL) {
  420. output = label;
  421. } else {
  422. while (*output == '\t')
  423. output++;
  424. }
  425. rootmenu = buildmenutree(level, label, output, file);
  426. }
  427. return rootmenu;
  428. }
  429. /* get next utf8 char from s return its codepoint and set next_ret to pointer to end of character */
  430. static FcChar32
  431. getnextutf8char(const char *s, const char **next_ret)
  432. {
  433. static const unsigned char utfbyte[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0};
  434. static const unsigned char utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
  435. static const FcChar32 utfmin[] = {0, 0x00, 0x80, 0x800, 0x10000};
  436. static const FcChar32 utfmax[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
  437. /* 0xFFFD is the replacement character, used to represent unknown characters */
  438. static const FcChar32 unknown = 0xFFFD;
  439. FcChar32 ucode; /* FcChar32 type holds 32 bits */
  440. size_t usize = 0; /* n' of bytes of the utf8 character */
  441. size_t i;
  442. *next_ret = s+1;
  443. /* get code of first byte of utf8 character */
  444. for (i = 0; i < sizeof utfmask; i++) {
  445. if (((unsigned char)*s & utfmask[i]) == utfbyte[i]) {
  446. usize = i;
  447. ucode = (unsigned char)*s & ~utfmask[i];
  448. break;
  449. }
  450. }
  451. /* if first byte is a continuation byte or is not allowed, return unknown */
  452. if (i == sizeof utfmask || usize == 0)
  453. return unknown;
  454. /* check the other usize-1 bytes */
  455. s++;
  456. for (i = 1; i < usize; i++) {
  457. *next_ret = s+1;
  458. /* if byte is nul or is not a continuation byte, return unknown */
  459. if (*s == '\0' || ((unsigned char)*s & utfmask[0]) != utfbyte[0])
  460. return unknown;
  461. /* 6 is the number of relevant bits in the continuation byte */
  462. ucode = (ucode << 6) | ((unsigned char)*s & ~utfmask[0]);
  463. s++;
  464. }
  465. /* check if ucode is invalid or in utf-16 surrogate halves */
  466. if (!BETWEEN(ucode, utfmin[usize], utfmax[usize])
  467. || BETWEEN (ucode, 0xD800, 0xDFFF))
  468. return unknown;
  469. return ucode;
  470. }
  471. /* get which font contains a given code point */
  472. static XftFont *
  473. getfontucode(FcChar32 ucode)
  474. {
  475. FcCharSet *fccharset = NULL;
  476. FcPattern *fcpattern = NULL;
  477. FcPattern *match = NULL;
  478. XftFont *retfont = NULL;
  479. XftResult result;
  480. size_t i;
  481. for (i = 0; i < dc.nfonts; i++)
  482. if (XftCharExists(dpy, dc.fonts[i], ucode) == FcTrue)
  483. return dc.fonts[i];
  484. /* create a charset containing our code point */
  485. fccharset = FcCharSetCreate();
  486. FcCharSetAddChar(fccharset, ucode);
  487. /* create a pattern akin to the dc.pattern but containing our charset */
  488. if (fccharset) {
  489. fcpattern = FcPatternDuplicate(dc.pattern);
  490. FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
  491. }
  492. /* find pattern matching fcpattern */
  493. if (fcpattern) {
  494. FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
  495. FcDefaultSubstitute(fcpattern);
  496. match = XftFontMatch(dpy, screen, fcpattern, &result);
  497. }
  498. /* if found a pattern, open its font */
  499. if (match) {
  500. retfont = XftFontOpenPattern(dpy, match);
  501. if (retfont && XftCharExists(dpy, retfont, ucode) == FcTrue) {
  502. if ((dc.fonts = realloc(dc.fonts, dc.nfonts+1)) == NULL)
  503. err(1, "realloc");
  504. dc.fonts[dc.nfonts] = retfont;
  505. return dc.fonts[dc.nfonts++];
  506. } else {
  507. XftFontClose(dpy, retfont);
  508. }
  509. }
  510. /* in case no fount was found, return the first one */
  511. return dc.fonts[0];
  512. }
  513. /* draw text into XftDraw, return width of text glyphs */
  514. static int
  515. drawtext(XftDraw *draw, XftColor *color, int x, int y, unsigned h, const char *text)
  516. {
  517. int textwidth = 0;
  518. while (*text) {
  519. XftFont *currfont;
  520. XGlyphInfo ext;
  521. FcChar32 ucode;
  522. const char *next;
  523. size_t len;
  524. ucode = getnextutf8char(text, &next);
  525. currfont = getfontucode(ucode);
  526. len = next - text;
  527. XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)text, len, &ext);
  528. textwidth += ext.xOff;
  529. if (draw) {
  530. int texty;
  531. texty = y + (h - (currfont->ascent + currfont->descent))/2 + currfont->ascent;
  532. XftDrawStringUtf8(draw, color, currfont, x, texty, (XftChar8 *)text, len);
  533. x += ext.xOff;
  534. }
  535. text = next;
  536. }
  537. return textwidth;
  538. }
  539. /* setup the height, width and icon of the items of a menu */
  540. static void
  541. setupitems(struct Menu *menu)
  542. {
  543. struct Item *item;
  544. int itemwidth;
  545. menu->w = config.width_pixels;
  546. menu->maxtextw = 0;
  547. for (item = menu->list; item != NULL; item = item->next) {
  548. item->y = menu->h;
  549. if (item->label == NULL) /* height for separator item */
  550. item->h = config.separator_pixels;
  551. else
  552. item->h = config.height_pixels;
  553. menu->h += item->h;
  554. if (item->label)
  555. item->textw = drawtext(NULL, NULL, 0, 0, 0, item->label);
  556. else
  557. item->textw = 0;
  558. /*
  559. * set menu width
  560. *
  561. * the item width depends on the size of its label (item->textw),
  562. * and it is only used to calculate the width of the menu (which
  563. * is equal to the width of the largest item).
  564. *
  565. * the horizontal padding appears 4 times through the width of a
  566. * item: before and after its icon, and before and after its triangle.
  567. * if the iflag is set (icons are disabled) then the horizontal
  568. * padding appears 3 times: before the label and around the triangle.
  569. */
  570. itemwidth = item->textw + config.triangle_width + config.horzpadding * 3;
  571. itemwidth += (iflag || !menu->hasicon) ? 0 : config.iconsize + config.horzpadding;
  572. menu->w = MAX(menu->w, itemwidth);
  573. menu->maxtextw = MAX(menu->maxtextw, item->textw);
  574. }
  575. }
  576. /* setup the position of a menu */
  577. static void
  578. setupmenupos(struct Menu *menu)
  579. {
  580. int width, height;
  581. width = menu->w + config.border_pixels * 2;
  582. height = menu->h + config.border_pixels * 2;
  583. if (menu->parent == NULL) { /* if root menu, calculate in respect to cursor */
  584. if (pflag || (config.posx >= mon.x && mon.x + mon.w - config.posx >= width))
  585. menu->x = config.posx;
  586. else if (config.posx > width)
  587. menu->x = config.posx - width;
  588. if (pflag || (config.posy >= mon.y && mon.y + mon.h - config.posy >= height))
  589. menu->y = config.posy;
  590. else if (mon.y + mon.h > height)
  591. menu->y = mon.y + mon.h - height;
  592. } else { /* else, calculate in respect to parent menu */
  593. int parentwidth;
  594. parentwidth = menu->parent->x + menu->parent->w + config.border_pixels + config.gap_pixels;
  595. if (mon.x + mon.w - parentwidth >= width)
  596. menu->x = parentwidth;
  597. else if (menu->parent->x > menu->w + config.border_pixels + config.gap_pixels)
  598. menu->x = menu->parent->x - menu->w - config.border_pixels - config.gap_pixels;
  599. if (mon.y + mon.h - (menu->caller->y + menu->parent->y) >= height)
  600. menu->y = menu->caller->y + menu->parent->y;
  601. else if (mon.y + mon.h > height)
  602. menu->y = mon.y + mon.h - height;
  603. }
  604. }
  605. /* recursivelly setup menu configuration and its pixmap */
  606. static void
  607. setupmenu(struct Menu *menu, XClassHint *classh)
  608. {
  609. char *title;
  610. struct Item *item;
  611. XWindowChanges changes;
  612. XSizeHints sizeh;
  613. XTextProperty wintitle;
  614. /* setup size and position of menus */
  615. setupitems(menu);
  616. setupmenupos(menu);
  617. /* update menu geometry */
  618. changes.border_width = config.border_pixels;
  619. changes.height = menu->h;
  620. changes.width = menu->w;
  621. changes.x = menu->x;
  622. changes.y = menu->y;
  623. XConfigureWindow(dpy, menu->win, CWBorderWidth | CWWidth | CWHeight | CWX | CWY, &changes);
  624. /* set window title (used if wflag is on) */
  625. if (menu->parent == NULL) {
  626. title = classh->res_name;
  627. } else {
  628. title = menu->caller->output;
  629. }
  630. XStringListToTextProperty(&title, 1, &wintitle);
  631. /* set window manager hints */
  632. sizeh.flags = USPosition | PMaxSize | PMinSize;
  633. sizeh.min_width = sizeh.max_width = menu->w;
  634. sizeh.min_height = sizeh.max_height = menu->h;
  635. XSetWMProperties(dpy, menu->win, &wintitle, NULL, NULL, 0, &sizeh, NULL, classh);
  636. /* set WM protocols and ewmh window properties */
  637. XSetWMProtocols(dpy, menu->win, &wmdelete, 1);
  638. XChangeProperty(dpy, menu->win, netatom[NetWMName], utf8string, 8,
  639. PropModeReplace, (unsigned char *)title, strlen(title));
  640. XChangeProperty(dpy, menu->win, netatom[NetWMWindowType], XA_ATOM, 32,
  641. PropModeReplace,
  642. (unsigned char *)&netatom[NetWMWindowTypePopupMenu], 1);
  643. /* calculate positions of submenus */
  644. for (item = menu->list; item != NULL; item = item->next) {
  645. if (item->submenu != NULL)
  646. setupmenu(item->submenu, classh);
  647. }
  648. }
  649. /* try to grab pointer, we may have to wait for another process to ungrab */
  650. static void
  651. grabpointer(void)
  652. {
  653. struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
  654. int i;
  655. for (i = 0; i < 1000; i++) {
  656. if (XGrabPointer(dpy, rootwin, True, ButtonPressMask,
  657. GrabModeAsync, GrabModeAsync, None,
  658. None, CurrentTime) == GrabSuccess)
  659. return;
  660. nanosleep(&ts, NULL);
  661. }
  662. errx(1, "could not grab pointer");
  663. }
  664. /* try to grab keyboard, we may have to wait for another process to ungrab */
  665. static void
  666. grabkeyboard(void)
  667. {
  668. struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
  669. int i;
  670. for (i = 0; i < 1000; i++) {
  671. if (XGrabKeyboard(dpy, rootwin, True, GrabModeAsync,
  672. GrabModeAsync, CurrentTime) == GrabSuccess)
  673. return;
  674. nanosleep(&ts, NULL);
  675. }
  676. errx(1, "could not grab keyboard");
  677. }
  678. /* try to grab focus, we may have to wait for another process to ungrab */
  679. static void
  680. grabfocus(Window win)
  681. {
  682. struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
  683. Window focuswin;
  684. int i, revertwin;
  685. for (i = 0; i < 100; ++i) {
  686. XGetInputFocus(dpy, &focuswin, &revertwin);
  687. if (focuswin == win)
  688. return;
  689. XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
  690. nanosleep(&ts, NULL);
  691. }
  692. errx(1, "cannot grab focus");
  693. }
  694. /* ungrab pointer and keyboard */
  695. static void
  696. ungrab(void)
  697. {
  698. XUngrabPointer(dpy, CurrentTime);
  699. XUngrabKeyboard(dpy, CurrentTime);
  700. }
  701. /* load and scale icon */
  702. static Imlib_Image
  703. loadicon(const char *file)
  704. {
  705. Imlib_Image icon;
  706. Imlib_Load_Error errcode;
  707. const char *errstr;
  708. int width;
  709. int height;
  710. int imgsize;
  711. icon = imlib_load_image_with_error_return(file, &errcode);
  712. if (*file == '\0') {
  713. warnx("could not load icon (file name is blank)");
  714. return NULL;
  715. } else if (icon == NULL) {
  716. switch (errcode) {
  717. case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
  718. errstr = "file does not exist";
  719. break;
  720. case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
  721. errstr = "file is directory";
  722. break;
  723. case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
  724. case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
  725. errstr = "permission denied";
  726. break;
  727. case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
  728. errstr = "unknown file format";
  729. break;
  730. case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
  731. errstr = "path too long";
  732. break;
  733. case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
  734. case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
  735. case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
  736. errstr = "improper path";
  737. break;
  738. case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
  739. errstr = "too many symbolic links";
  740. break;
  741. case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
  742. errstr = "out of memory";
  743. break;
  744. case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS:
  745. errstr = "out of file descriptors";
  746. break;
  747. default:
  748. errstr = "unknown error";
  749. break;
  750. }
  751. warnx("could not load icon (%s): %s", errstr, file);
  752. return NULL;
  753. }
  754. imlib_context_set_image(icon);
  755. width = imlib_image_get_width();
  756. height = imlib_image_get_height();
  757. imgsize = MIN(width, height);
  758. icon = imlib_create_cropped_scaled_image(0, 0, imgsize, imgsize,
  759. config.iconsize,
  760. config.iconsize);
  761. return icon;
  762. }
  763. /* draw pixmap for the selected and unselected version of each item on menu */
  764. static void
  765. drawitems(struct Menu *menu)
  766. {
  767. XftDraw *dsel, *dunsel;
  768. struct Item *item;
  769. int textx;
  770. int x, y;
  771. for (item = menu->list; item != NULL; item = item->next) {
  772. item->unsel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
  773. DefaultDepth(dpy, screen));
  774. XSetForeground(dpy, dc.gc, dc.normal[ColorBG].pixel);
  775. XFillRectangle(dpy, item->unsel, dc.gc, 0, 0, menu->w, item->h);
  776. if (item->label == NULL) { /* item is separator */
  777. y = item->h/2;
  778. XSetForeground(dpy, dc.gc, dc.separator.pixel);
  779. XDrawLine(dpy, item->unsel, dc.gc, config.horzpadding, y,
  780. menu->w - config.horzpadding, y);
  781. item->sel = item->unsel;
  782. } else {
  783. item->sel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
  784. DefaultDepth(dpy, screen));
  785. XSetForeground(dpy, dc.gc, dc.selected[ColorBG].pixel);
  786. XFillRectangle(dpy, item->sel, dc.gc, 0, 0, menu->w, item->h);
  787. /* draw text */
  788. textx = config.horzpadding;
  789. textx += (iflag || !menu->hasicon) ? 0 : config.horzpadding + config.iconsize;
  790. switch (config.alignment) {
  791. case CenterAlignment:
  792. textx += (menu->maxtextw - item->textw) / 2;
  793. break;
  794. case RightAlignment:
  795. textx += menu->maxtextw - item->textw;
  796. break;
  797. default:
  798. break;
  799. }
  800. dsel = XftDrawCreate(dpy, item->sel, visual, colormap);
  801. dunsel = XftDrawCreate(dpy, item->unsel, visual, colormap);
  802. XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
  803. drawtext(dsel, &dc.selected[ColorFG], textx, 0, item->h, item->label);
  804. XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
  805. drawtext(dunsel, &dc.normal[ColorFG], textx, 0, item->h, item->label);
  806. XftDrawDestroy(dsel);
  807. XftDrawDestroy(dunsel);
  808. /* draw triangle */
  809. if (item->submenu != NULL) {
  810. x = menu->w - config.triangle_width - config.horzpadding;
  811. y = (item->h - config.triangle_height + 1) / 2;
  812. XPoint triangle[] = {
  813. {x, y},
  814. {x + config.triangle_width, y + config.triangle_height/2},
  815. {x, y + config.triangle_height},
  816. {x, y}
  817. };
  818. XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
  819. XFillPolygon(dpy, item->sel, dc.gc, triangle, LEN(triangle),
  820. Convex, CoordModeOrigin);
  821. XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
  822. XFillPolygon(dpy, item->unsel, dc.gc, triangle, LEN(triangle),
  823. Convex, CoordModeOrigin);
  824. }
  825. /* try to load icon */
  826. if (item->file && !iflag) {
  827. item->icon = loadicon(item->file);
  828. free(item->file);
  829. }
  830. /* draw icon if properly loaded */
  831. if (item->icon) {
  832. imlib_context_set_image(item->icon);
  833. imlib_context_set_drawable(item->sel);
  834. imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
  835. imlib_context_set_drawable(item->unsel);
  836. imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
  837. imlib_context_set_image(item->icon);
  838. imlib_free_image();
  839. }
  840. }
  841. }
  842. }
  843. /* copy pixmaps of items of the current menu and of its ancestors into menu window */
  844. static void
  845. drawmenus(struct Menu *currmenu)
  846. {
  847. struct Menu *menu;
  848. struct Item *item;
  849. for (menu = currmenu; menu != NULL; menu = menu->parent) {
  850. if (!menu->drawn) {
  851. drawitems(menu);
  852. menu->drawn = 1;
  853. }
  854. for (item = menu->list; item != NULL; item = item->next) {
  855. if (item == menu->selected)
  856. XCopyArea(dpy, item->sel, menu->win, dc.gc, 0, 0,
  857. menu->w, item->h, 0, item->y);
  858. else
  859. XCopyArea(dpy, item->unsel, menu->win, dc.gc, 0, 0,
  860. menu->w, item->h, 0, item->y);
  861. }
  862. }
  863. }
  864. /* umap previous menus and map current menu and its parents */
  865. static void
  866. mapmenu(struct Menu *currmenu)
  867. {
  868. static struct Menu *prevmenu = NULL;
  869. struct Menu *menu, *menu_;
  870. struct Menu *lcamenu; /* lowest common ancestor menu */
  871. unsigned minlevel; /* level of the closest to root menu */
  872. unsigned maxlevel; /* level of the closest to root menu */
  873. /* do not remap current menu if it wasn't updated*/
  874. if (prevmenu == currmenu)
  875. return;
  876. /* if this is the first time mapping, skip calculations */
  877. if (prevmenu == NULL) {
  878. XMapWindow(dpy, currmenu->win);
  879. prevmenu = currmenu;
  880. return;
  881. }
  882. /* find lowest common ancestor menu */
  883. minlevel = MIN(currmenu->level, prevmenu->level);
  884. maxlevel = MAX(currmenu->level, prevmenu->level);
  885. if (currmenu->level == maxlevel) {
  886. menu = currmenu;
  887. menu_ = prevmenu;
  888. } else {
  889. menu = prevmenu;
  890. menu_ = currmenu;
  891. }
  892. while (menu->level > minlevel)
  893. menu = menu->parent;
  894. while (menu != menu_) {
  895. menu = menu->parent;
  896. menu_ = menu_->parent;
  897. }
  898. lcamenu = menu;
  899. /* unmap menus from currmenu (inclusive) until lcamenu (exclusive) */
  900. for (menu = prevmenu; menu != lcamenu; menu = menu->parent) {
  901. menu->selected = NULL;
  902. XUnmapWindow(dpy, menu->win);
  903. }
  904. /* map menus from currmenu (inclusive) until lcamenu (exclusive) */
  905. for (menu = currmenu; menu != lcamenu; menu = menu->parent) {
  906. if (wflag) {
  907. setupmenupos(menu);
  908. XMoveWindow(dpy, menu->win, menu->x, menu->y);
  909. }
  910. XMapWindow(dpy, menu->win);
  911. }
  912. prevmenu = currmenu;
  913. grabfocus(currmenu->win);
  914. }
  915. /* get menu of given window */
  916. static struct Menu *
  917. getmenu(struct Menu *currmenu, Window win)
  918. {
  919. struct Menu *menu;
  920. for (menu = currmenu; menu != NULL; menu = menu->parent)
  921. if (menu->win == win)
  922. return menu;
  923. return NULL;
  924. }
  925. /* get item of given menu and position */
  926. static struct Item *
  927. getitem(struct Menu *menu, int y)
  928. {
  929. struct Item *item;
  930. if (menu == NULL)
  931. return NULL;
  932. for (item = menu->list; item != NULL; item = item->next)
  933. if (y >= item->y && y <= item->y + item->h)
  934. return item;
  935. return NULL;
  936. }
  937. /* cycle through the items; non-zero direction is next, zero is prev */
  938. static struct Item *
  939. itemcycle(struct Menu *currmenu, int direction)
  940. {
  941. struct Item *item = NULL;
  942. struct Item *lastitem;
  943. for (lastitem = currmenu->list; lastitem && lastitem->next; lastitem = lastitem->next)
  944. ;
  945. /* select item (either separator or labeled item) in given direction */
  946. switch (direction) {
  947. case ITEMNEXT:
  948. if (currmenu->selected == NULL)
  949. item = currmenu->list;
  950. else if (currmenu->selected->next != NULL)
  951. item = currmenu->selected->next;
  952. break;
  953. case ITEMPREV:
  954. if (currmenu->selected == NULL)
  955. item = lastitem;
  956. else if (currmenu->selected->prev != NULL)
  957. item = currmenu->selected->prev;
  958. break;
  959. case ITEMFIRST:
  960. item = currmenu->list;
  961. break;
  962. case ITEMLAST:
  963. item = lastitem;
  964. break;
  965. }
  966. /*
  967. * the selected item can be a separator
  968. * let's select the closest labeled item (ie., one that isn't a separator)
  969. */
  970. switch (direction) {
  971. case ITEMNEXT:
  972. case ITEMFIRST:
  973. while (item != NULL && item->label == NULL)
  974. item = item->next;
  975. if (item == NULL)
  976. item = currmenu->list;
  977. break;
  978. case ITEMPREV:
  979. case ITEMLAST:
  980. while (item != NULL && item->label == NULL)
  981. item = item->prev;
  982. if (item == NULL)
  983. item = lastitem;
  984. break;
  985. }
  986. return item;
  987. }
  988. /* check if button is used to open a item on click */
  989. static int
  990. isclickbutton(unsigned int button)
  991. {
  992. if (button == Button1)
  993. return 1;
  994. if (!rflag && button == Button3)
  995. return 1;
  996. return 0;
  997. }
  998. /* append buf into text */
  999. static int
  1000. append(char *text, char *buf, size_t textsize, size_t buflen)
  1001. {
  1002. size_t textlen;
  1003. textlen = strlen(text);
  1004. if (iscntrl(*buf))
  1005. return 0;
  1006. if (textlen + buflen > textsize - 1)
  1007. return 0;
  1008. if (buflen < 1)
  1009. return 0;
  1010. memcpy(text + textlen, buf, buflen);
  1011. text[textlen + buflen] = '\0';
  1012. return 1;
  1013. }
  1014. /* get item in menu matching text from given direction (or from beginning, if dir = 0) */
  1015. static struct Item *
  1016. matchitem(struct Menu *menu, char *text, int dir)
  1017. {
  1018. struct Item *item, *lastitem;
  1019. char *s;
  1020. size_t textlen;
  1021. for (lastitem = menu->list; lastitem && lastitem->next; lastitem = lastitem->next)
  1022. ;
  1023. textlen = strlen(text);
  1024. if (dir < 0) {
  1025. if (menu->selected && menu->selected->prev)
  1026. item = menu->selected->prev;
  1027. else
  1028. item = lastitem;
  1029. } else if (dir > 0) {
  1030. if (menu->selected && menu->selected->next)
  1031. item = menu->selected->next;
  1032. else
  1033. item = menu->list;
  1034. } else {
  1035. item = menu->list;
  1036. }
  1037. /* find next item from selected item */
  1038. for ( ; item; item = (dir < 0) ? item->prev : item->next)
  1039. for (s = item->label; s && *s; s++)
  1040. if (strncasecmp(s, text, textlen) == 0)
  1041. return item;
  1042. /* if not found, try to find from the beginning/end of list */
  1043. if (dir > 0) {
  1044. for (item = menu->list ; item; item = item->next) {
  1045. for (s = item->label; s && *s; s++) {
  1046. if (strncasecmp(s, text, textlen) == 0) {
  1047. return item;
  1048. }
  1049. }
  1050. }
  1051. } else {
  1052. for (item = lastitem ; item; item = item->prev) {
  1053. for (s = item->label; s && *s; s++) {
  1054. if (strncasecmp(s, text, textlen) == 0) {
  1055. return item;
  1056. }
  1057. }
  1058. }
  1059. }
  1060. return NULL;
  1061. }
  1062. /* check keysyms defined on config.h */
  1063. static KeySym
  1064. normalizeksym(KeySym ksym)
  1065. {
  1066. if (ksym == KSYMFIRST)
  1067. return XK_Home;
  1068. if (ksym == KSYMLAST)
  1069. return XK_End;
  1070. if (ksym == KSYMUP)
  1071. return XK_Up;
  1072. if (ksym == KSYMDOWN)
  1073. return XK_Down;
  1074. if (ksym == KSYMLEFT)
  1075. return XK_Left;
  1076. if (ksym == KSYMRIGHT)
  1077. return XK_Right;
  1078. return ksym;
  1079. }
  1080. /* run event loop */
  1081. static void
  1082. run(struct Menu *currmenu)
  1083. {
  1084. char text[BUFSIZ];
  1085. char buf[32];
  1086. struct Menu *menu;
  1087. struct Item *item;
  1088. struct Item *previtem = NULL;
  1089. struct Item *lastitem, *select;
  1090. KeySym ksym;
  1091. Status status;
  1092. XEvent ev;
  1093. int action;
  1094. int len;
  1095. int i;
  1096. text[0] = '\0';
  1097. mapmenu(currmenu);
  1098. while (!XNextEvent(dpy, &ev)) {
  1099. if (XFilterEvent(&ev, None))
  1100. continue;
  1101. action = ACTION_NOP;
  1102. switch(ev.type) {
  1103. case Expose:
  1104. if (ev.xexpose.count == 0)
  1105. action = ACTION_DRAW;
  1106. break;
  1107. case MotionNotify:
  1108. menu = getmenu(currmenu, ev.xbutton.window);
  1109. item = getitem(menu, ev.xbutton.y);
  1110. if (menu == NULL || item == NULL || previtem == item)
  1111. break;
  1112. previtem = item;
  1113. select = menu->selected = item;
  1114. if (item->submenu != NULL) {
  1115. currmenu = item->submenu;
  1116. select = NULL;
  1117. } else {
  1118. currmenu = menu;
  1119. }
  1120. action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
  1121. break;
  1122. case ButtonRelease:
  1123. if (!isclickbutton(ev.xbutton.button))
  1124. break;
  1125. menu = getmenu(currmenu, ev.xbutton.window);
  1126. item = getitem(menu, ev.xbutton.y);
  1127. if (menu == NULL || item == NULL)
  1128. break;
  1129. enteritem:
  1130. if (item->label == NULL)
  1131. break; /* ignore separators */
  1132. if (item->submenu != NULL) {
  1133. currmenu = item->submenu;
  1134. } else {
  1135. printf("%s\n", item->output);
  1136. return;
  1137. }
  1138. select = currmenu->list;
  1139. action = ACTION_CLEAR | ACTION_SELECT | ACTION_MAP | ACTION_DRAW;
  1140. break;
  1141. case ButtonPress:
  1142. menu = getmenu(currmenu, ev.xbutton.window);
  1143. if (menu == NULL)
  1144. return;
  1145. break;
  1146. case KeyPress:
  1147. len = XmbLookupString(currmenu->xic, &ev.xkey, buf, sizeof buf, &ksym, &status);
  1148. switch(status) {
  1149. default: /* XLookupNone, XBufferOverflow */
  1150. continue;
  1151. case XLookupChars:
  1152. goto append;
  1153. case XLookupKeySym: /* FALLTHROUGH */
  1154. case XLookupBoth:
  1155. break;
  1156. }
  1157. /* esc closes xmenu when current menu is the root menu */
  1158. if (ksym == XK_Escape && currmenu->parent == NULL)
  1159. return;
  1160. /* Shift-Tab = ISO_Left_Tab */
  1161. if (ksym == XK_Tab && (ev.xkey.state & ShiftMask))
  1162. ksym = XK_ISO_Left_Tab;
  1163. /* cycle through menu */
  1164. select = NULL;
  1165. ksym = normalizeksym(ksym);
  1166. switch (ksym) {
  1167. case XK_Home:
  1168. select = itemcycle(currmenu, ITEMFIRST);
  1169. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1170. break;
  1171. case XK_End:
  1172. select = itemcycle(currmenu, ITEMLAST);
  1173. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1174. break;
  1175. case XK_ISO_Left_Tab:
  1176. if (*text) {
  1177. select = matchitem(currmenu, text, -1);
  1178. action = ACTION_SELECT | ACTION_DRAW;
  1179. break;
  1180. }
  1181. /* FALLTHROUGH */
  1182. case XK_Up:
  1183. select = itemcycle(currmenu, ITEMPREV);
  1184. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1185. break;
  1186. case XK_Tab:
  1187. if (*text) {
  1188. select = matchitem(currmenu, text, 1);
  1189. action = ACTION_SELECT | ACTION_DRAW;
  1190. break;
  1191. }
  1192. /* FALLTHROUGH */
  1193. case XK_Down:
  1194. select = itemcycle(currmenu, ITEMNEXT);
  1195. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1196. break;
  1197. case XK_1: case XK_2: case XK_3: case XK_4: case XK_5: case XK_6: case XK_7: case XK_8: case XK_9:
  1198. item = itemcycle(currmenu, ITEMFIRST);
  1199. lastitem = itemcycle(currmenu, ITEMLAST);
  1200. for (int i = ksym - XK_1; i > 0 && item != lastitem; i--) {
  1201. currmenu->selected = item;
  1202. item = itemcycle(currmenu, ITEMNEXT);
  1203. }
  1204. select = item;
  1205. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1206. break;
  1207. case XK_Return: case XK_Right:
  1208. if (currmenu->selected) {
  1209. item = currmenu->selected;
  1210. goto enteritem;
  1211. }
  1212. break;
  1213. case XK_Escape: case XK_Left:
  1214. if (currmenu->parent) {
  1215. select = currmenu->parent->selected;
  1216. currmenu = currmenu->parent;
  1217. action = ACTION_CLEAR | ACTION_MAP | ACTION_SELECT | ACTION_DRAW;
  1218. }
  1219. break;
  1220. case XK_BackSpace: case XK_Clear: case XK_Delete:
  1221. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1222. break;
  1223. default:
  1224. append:
  1225. if (*buf == '\0' || iscntrl(*buf))
  1226. break;
  1227. for (i = 0; i < 2; i++) {
  1228. append(text, buf, sizeof text, len);
  1229. if ((select = matchitem(currmenu, text, 0)))
  1230. break;
  1231. text[0] = '\0';
  1232. }
  1233. action = ACTION_SELECT | ACTION_DRAW;
  1234. break;
  1235. }
  1236. break;
  1237. case LeaveNotify:
  1238. previtem = NULL;
  1239. select = NULL;
  1240. action = ACTION_CLEAR | ACTION_SELECT | ACTION_DRAW;
  1241. break;
  1242. case ConfigureNotify:
  1243. menu = getmenu(currmenu, ev.xconfigure.window);
  1244. if (menu == NULL)
  1245. break;
  1246. menu->x = ev.xconfigure.x;
  1247. menu->y = ev.xconfigure.y;
  1248. break;
  1249. case ClientMessage:
  1250. if ((unsigned long) ev.xclient.data.l[0] != wmdelete)
  1251. break;
  1252. /* user closed window */
  1253. menu = getmenu(currmenu, ev.xclient.window);
  1254. if (menu->parent == NULL)
  1255. return; /* closing the root menu closes the program */
  1256. currmenu = menu->parent;
  1257. action = ACTION_MAP;
  1258. break;
  1259. }
  1260. if (action & ACTION_CLEAR)
  1261. text[0] = '\0';
  1262. if (action & ACTION_SELECT)
  1263. currmenu->selected = select;
  1264. if (action & ACTION_MAP)
  1265. mapmenu(currmenu);
  1266. if (action & ACTION_DRAW)
  1267. drawmenus(currmenu);
  1268. }
  1269. }
  1270. /* recursivelly free pixmaps and destroy windows */
  1271. static void
  1272. cleanmenu(struct Menu *menu)
  1273. {
  1274. struct Item *item;
  1275. struct Item *tmp;
  1276. item = menu->list;
  1277. while (item != NULL) {
  1278. if (item->submenu != NULL)
  1279. cleanmenu(item->submenu);
  1280. tmp = item;
  1281. if (menu->drawn) {
  1282. XFreePixmap(dpy, item->unsel);
  1283. if (tmp->label != NULL)
  1284. XFreePixmap(dpy, item->sel);
  1285. }
  1286. if (tmp->label != tmp->output)
  1287. free(tmp->label);
  1288. free(tmp->output);
  1289. item = item->next;
  1290. free(tmp);
  1291. }
  1292. XDestroyWindow(dpy, menu->win);
  1293. free(menu);
  1294. }
  1295. /* cleanup draw context */
  1296. static void
  1297. cleandc(void)
  1298. {
  1299. size_t i;
  1300. XftColorFree(dpy, visual, colormap, &dc.normal[ColorBG]);
  1301. XftColorFree(dpy, visual, colormap, &dc.normal[ColorFG]);
  1302. XftColorFree(dpy, visual, colormap, &dc.selected[ColorBG]);
  1303. XftColorFree(dpy, visual, colormap, &dc.selected[ColorFG]);
  1304. XftColorFree(dpy, visual, colormap, &dc.separator);
  1305. XftColorFree(dpy, visual, colormap, &dc.border);
  1306. for (i = 0; i < dc.nfonts; i++)
  1307. XftFontClose(dpy, dc.fonts[i]);
  1308. XFreeGC(dpy, dc.gc);
  1309. }
  1310. /* xmenu: generate menu from stdin and print selected entry to stdout */
  1311. int
  1312. main(int argc, char *argv[])
  1313. {
  1314. struct Menu *rootmenu;
  1315. XClassHint classh;
  1316. /* open connection to server and set X variables */
  1317. if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
  1318. warnx("warning: no locale support");
  1319. if ((dpy = XOpenDisplay(NULL)) == NULL)
  1320. errx(1, "could not open display");
  1321. screen = DefaultScreen(dpy);
  1322. visual = DefaultVisual(dpy, screen);
  1323. rootwin = RootWindow(dpy, screen);
  1324. colormap = DefaultColormap(dpy, screen);
  1325. XrmInitialize();
  1326. if ((xrm = XResourceManagerString(dpy)) != NULL)
  1327. xdb = XrmGetStringDatabase(xrm);
  1328. if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
  1329. errx(1, "XOpenIM: could not open input device");
  1330. /* process configuration and window class */
  1331. getresources();
  1332. classh.res_class = PROGNAME;
  1333. classh.res_name = getoptions(argc, argv);
  1334. /* imlib2 stuff */
  1335. if (!iflag) {
  1336. imlib_set_cache_size(2048 * 1024);
  1337. imlib_context_set_dither(1);
  1338. imlib_context_set_display(dpy);
  1339. imlib_context_set_visual(visual);
  1340. imlib_context_set_colormap(colormap);
  1341. }
  1342. /* initializers */
  1343. initmonitor();
  1344. initdc();
  1345. initiconsize();
  1346. initatoms();
  1347. /* generate menus and set them up */
  1348. rootmenu = parsestdin();
  1349. if (rootmenu == NULL)
  1350. errx(1, "no menu generated");
  1351. setupmenu(rootmenu, &classh);
  1352. /* grab mouse and keyboard */
  1353. if (!wflag) {
  1354. grabpointer();
  1355. grabkeyboard();
  1356. }
  1357. /* run event loop */
  1358. run(rootmenu);
  1359. /* clean stuff */
  1360. ungrab();
  1361. cleanmenu(rootmenu);
  1362. cleandc();
  1363. XrmDestroyDatabase(xdb);
  1364. XCloseDisplay(dpy);
  1365. return 0;
  1366. }