A mirror of phillbush's xmenu.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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