+grabfocus(Window win)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
+ Window focuswin;
+ int i, revertwin;
+
+ for (i = 0; i < 100; ++i) {
+ XGetInputFocus(dpy, &focuswin, &revertwin);
+ if (focuswin == win)
+ return;
+ XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
+ nanosleep(&ts, NULL);
+ }
+ errx(1, "cannot grab focus");
+}
+
+/* ungrab pointer and keyboard */
+static void
+ungrab(void)
+{
+ XUngrabPointer(dpy, CurrentTime);
+ XUngrabKeyboard(dpy, CurrentTime);
+}
+
+/* check if path is absolute or relative to current directory */
+static int
+isabsolute(const char *s)
+{
+ return s[0] == '/' || (s[0] == '.' && (s[1] == '/' || (s[1] == '.' && s[2] == '/')));
+}
+
+/* load and scale icon */
+static Imlib_Image
+loadicon(const char *file)
+{
+ Imlib_Image icon;
+ Imlib_Load_Error errcode;
+ char path[PATH_MAX];
+ const char *errstr;
+ int width;
+ int height;
+ int imgsize;
+ int i;
+
+ if (*file == '\0') {
+ warnx("could not load icon (file name is blank)");
+ return NULL;
+ }
+ if (isabsolute(file))
+ icon = imlib_load_image_with_error_return(file, &errcode);
+ else {
+ for (i = 0; i < niconpaths; i++) {
+ snprintf(path, sizeof(path), "%s/%s", iconpaths[i], file);
+ icon = imlib_load_image_with_error_return(path, &errcode);
+ if (icon != NULL || errcode != IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST) {
+ break;
+ }
+ }
+ }
+ if (icon == NULL) {
+ switch (errcode) {
+ case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
+ errstr = "file does not exist";
+ break;
+ case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
+ errstr = "file is directory";
+ break;
+ case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
+ case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
+ errstr = "permission denied";
+ break;
+ case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
+ errstr = "unknown file format";
+ break;
+ case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
+ errstr = "path too long";
+ break;
+ case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
+ case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
+ case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
+ errstr = "improper path";
+ break;
+ case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
+ errstr = "too many symbolic links";
+ break;
+ case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
+ errstr = "out of memory";
+ break;
+ case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS:
+ errstr = "out of file descriptors";
+ break;
+ default:
+ errstr = "unknown error";
+ break;
+ }
+ warnx("could not load icon (%s): %s", errstr, file);
+ return NULL;
+ }
+
+ imlib_context_set_image(icon);
+
+ width = imlib_image_get_width();
+ height = imlib_image_get_height();
+ imgsize = min(width, height);
+
+ icon = imlib_create_cropped_scaled_image(0, 0, imgsize, imgsize,
+ config.iconsize,
+ config.iconsize);
+
+ return icon;
+}
+
+/* draw pixmap for the selected and unselected version of each item on menu */
+static void
+drawitems(struct Menu *menu)
+{
+ XftDraw *dsel, *dunsel;
+ struct Item *item;
+ int textx;
+ int x, y;
+
+ for (item = menu->list; item != NULL; item = item->next) {
+ item->unsel = XCreatePixmap(dpy, menu->win, menu->w, item->h, depth);
+
+ XSetForeground(dpy, dc.gc, dc.normal[ColorBG].pixel);
+ XFillRectangle(dpy, item->unsel, dc.gc, 0, 0, menu->w, item->h);
+
+ if (item->label == NULL) { /* item is separator */
+ y = item->h / 2;
+ XSetForeground(dpy, dc.gc, dc.separator.pixel);
+ XDrawLine(dpy, item->unsel, dc.gc, config.horzpadding, y,
+ menu->w - config.horzpadding, y);
+
+ item->sel = item->unsel;
+ } else {
+ item->sel = XCreatePixmap(dpy, menu->win, menu->w, item->h, depth);
+ XSetForeground(dpy, dc.gc, dc.selected[ColorBG].pixel);
+ XFillRectangle(dpy, item->sel, dc.gc, 0, 0, menu->w, item->h);
+
+ /* draw text */
+ textx = config.horzpadding;
+ textx += (iflag || !menu->hasicon) ? 0 : config.horzpadding + config.iconsize;
+ switch (config.alignment) {
+ case CenterAlignment:
+ textx += (menu->maxtextw - item->textw) / 2;
+ break;
+ case RightAlignment:
+ textx += menu->maxtextw - item->textw;
+ break;
+ default:
+ break;
+ }
+ dsel = XftDrawCreate(dpy, item->sel, visual, colormap);
+ dunsel = XftDrawCreate(dpy, item->unsel, visual, colormap);
+ XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
+ drawtext(dsel, &dc.selected[ColorFG], textx, 0, item->h, item->label);
+ XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
+ drawtext(dunsel, &dc.normal[ColorFG], textx, 0, item->h, item->label);
+ XftDrawDestroy(dsel);
+ XftDrawDestroy(dunsel);
+
+ /* draw triangle */
+ if (item->submenu != NULL) {
+ x = menu->w - config.triangle_width - config.horzpadding;
+ y = (item->h - config.triangle_height + 1) / 2;
+
+ XPoint triangle[] = {
+ {x, y},
+ {x + config.triangle_width, y + config.triangle_height/2},
+ {x, y + config.triangle_height},
+ {x, y}
+ };
+
+ XSetForeground(dpy, dc.gc, dc.selected[ColorFG].pixel);
+ XFillPolygon(dpy, item->sel, dc.gc, triangle, LEN(triangle),
+ Convex, CoordModeOrigin);
+ XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
+ XFillPolygon(dpy, item->unsel, dc.gc, triangle, LEN(triangle),
+ Convex, CoordModeOrigin);
+ }
+
+ /* try to load icon */
+ if (item->file && !iflag) {
+ item->icon = loadicon(item->file);
+ free(item->file);
+ }
+
+ /* draw icon if properly loaded */
+ if (item->icon) {
+ imlib_context_set_image(item->icon);
+ imlib_context_set_drawable(item->sel);
+ imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
+ imlib_context_set_drawable(item->unsel);
+ imlib_render_image_on_drawable(config.horzpadding, config.iconpadding);
+ imlib_context_set_image(item->icon);
+ imlib_free_image();
+ }
+ }
+ }
+}
+
+/* copy pixmaps of items of the current menu and of its ancestors into menu window */
+static void
+drawmenus(struct Menu *currmenu)
+{
+ struct Menu *menu;
+ struct Item *item;
+ int y0, y;
+ int maxh;
+
+ for (menu = currmenu; menu != NULL; menu = menu->parent) {
+ if (!menu->drawn) {
+ drawitems(menu);
+ menu->drawn = 1;
+ }
+ if (menu->overflow && menu->selected != NULL) {
+ maxh = menu->h - config.height_pixels * 2;
+ while (menu->first->next != NULL &&
+ menu->selected->y >= menu->first->y + maxh) {
+ menu->first = menu->first->next;
+ }
+ while (menu->first->prev != NULL &&
+ menu->selected->y < menu->first->y) {
+ menu->first = menu->first->prev;
+ }
+ }
+ y = menu->first->y;
+ y0 = menu->overflow ? config.height_pixels : 0;
+ for (item = menu->first; item != NULL; item = item->next) {
+ if (menu->overflow && item->y - y + item->h > menu->h - config.height_pixels * 2)
+ break;
+ if (item == menu->selected) {
+ XCopyArea(dpy, item->sel, menu->win, dc.gc, 0, 0, menu->w, item->h, 0, y0 + item->y - y);
+ } else {
+ XCopyArea(dpy, item->unsel, menu->win, dc.gc, 0, 0, menu->w, item->h, 0, y0 + item->y - y);
+ }
+ }
+ }
+}
+
+/* unmap current menu and its parents */
+static void
+unmapmenu(struct Menu *currmenu)
+{
+ struct Menu *menu;
+
+ for (menu = currmenu; menu != NULL; menu = menu->parent) {
+ menu->selected = NULL;
+ XUnmapWindow(dpy, menu->win);
+ }
+}
+
+/* umap previous menus and map current menu and its parents */
+static struct Menu *
+mapmenu(struct Menu *currmenu, struct Menu *prevmenu, struct Monitor *mon)