X-Git-Url: https://git.subgeniuskitty.com/xmenu/.git/blobdiff_plain/08f165897c0d42fb8177344fdb1bbaf4fc944181..697f63c47427bcca9115e74859ce4debe788fdc5:/xmenu.c diff --git a/xmenu.c b/xmenu.c index 41303cc..82617d7 100644 --- a/xmenu.c +++ b/xmenu.c @@ -5,6 +5,12 @@ #include #include #include +#include +#include +#include + +#define ITEMPREV 0 +#define ITEMNEXT 1 /* macros */ #define LEN(x) (sizeof (x) / sizeof (x[0])) @@ -16,14 +22,13 @@ enum {ColorFG, ColorBG, ColorLast}; /* draw context structure */ struct DC { - unsigned long unpressed[ColorLast]; - unsigned long pressed[ColorLast]; - unsigned long decoration[ColorLast]; + XftColor normal[ColorLast]; + XftColor selected[ColorLast]; + XftColor decoration[ColorLast]; Drawable d; GC gc; - XFontStruct *font; - int fonth; + XftFont *font; }; /* menu geometry structure */ @@ -48,6 +53,7 @@ struct Item { int y; /* item y position relative to menu */ int h; /* item height */ size_t labellen; /* strlen(label) */ + struct Item *prev; /* previous item */ struct Item *next; /* next item */ struct Menu *submenu; /* submenu spawned by clicking on item */ }; @@ -61,11 +67,13 @@ struct Menu { int x, y, w, h; /* menu geometry */ unsigned level; /* menu level relative to root */ Drawable pixmap; /* pixmap to draw the menu on */ + XftDraw *draw; Window win; /* menu window to map on the screen */ }; /* function declarations */ -static unsigned long getcolor(const char *s); +static void getcolor(const char *s, XftColor *color); +static void getresources(void); static void setupdc(void); static void setupgeom(void); static void setupgrab(void); @@ -85,6 +93,7 @@ static void usage(void); /* X variables */ static Colormap colormap; static Display *dpy; +static Visual *visual; static Window rootwin; static int screen; static struct DC dc; @@ -124,13 +133,16 @@ main(int argc, char *argv[]) if ((dpy = XOpenDisplay(NULL)) == NULL) errx(1, "cannot open display"); screen = DefaultScreen(dpy); + visual = DefaultVisual(dpy, screen); rootwin = RootWindow(dpy, screen); colormap = DefaultColormap(dpy, screen); /* setup */ + getresources(); setupdc(); setupgeom(); - setupgrab(); + if (override_redirect) + setupgrab(); /* generate menus and recalculate them */ parsestdin(); @@ -151,15 +163,58 @@ main(int argc, char *argv[]) return 0; } -/* get color from color string */ -static unsigned long -getcolor(const char *s) +/* read xrdb for configuration options */ +static void +getresources(void) { - XColor color; + char *xrm; + long n; + + XrmInitialize(); + if ((xrm = XResourceManagerString(dpy))) { + char *type; + XrmDatabase xdb; + XrmValue xval; + + xdb = XrmGetStringDatabase(xrm); + + if (XrmGetResource(xdb, "xmenu.menuborder", "*", &type, &xval) == True) + if ((n = strtol(xval.addr, NULL, 10)) > 0) + menuborder = n; + if (XrmGetResource(xdb, "xmenu.separatorsize", "*", &type, &xval) == True) + if ((n = strtol(xval.addr, NULL, 10)) > 0) + separatorsize = n; + if (XrmGetResource(xdb, "xmenu.itemborder", "*", &type, &xval) == True) + if ((n = strtol(xval.addr, NULL, 10)) > 0) + itemborder = n; + if (XrmGetResource(xdb, "xmenu.width", "*", &type, &xval) == True) + if ((n = strtol(xval.addr, NULL, 10)) > 0) + width = n; + if (XrmGetResource(xdb, "xmenu.background", "*", &type, &xval) == True) + background = strdup(xval.addr); + if (XrmGetResource(xdb, "xmenu.foreground", "*", &type, &xval) == True) + foreground = strdup(xval.addr); + if (XrmGetResource(xdb, "xmenu.selbackground", "*", &type, &xval) == True) + selbackground = strdup(xval.addr); + if (XrmGetResource(xdb, "xmenu.selforeground", "*", &type, &xval) == True) + selforeground = strdup(xval.addr); + if (XrmGetResource(xdb, "xmenu.separator", "*", &type, &xval) == True) + separator = strdup(xval.addr); + if (XrmGetResource(xdb, "xmenu.border", "*", &type, &xval) == True) + border = strdup(xval.addr); + if (XrmGetResource(xdb, "xmenu.font", "*", &type, &xval) == True) + font = strdup(xval.addr); + + XrmDestroyDatabase(xdb); + } +} - if(!XAllocNamedColor(dpy, colormap, s, &color, &color)) +/* get color from color string */ +static void +getcolor(const char *s, XftColor *color) +{ + if(!XftColorAllocName(dpy, visual, colormap, s, color)) errx(1, "cannot allocate color: %s", s); - return color.pixel; } /* init draw context */ @@ -167,40 +222,43 @@ static void setupdc(void) { /* get color pixels */ - dc.unpressed[ColorBG] = getcolor(UNPRESSEDBG); - dc.unpressed[ColorFG] = getcolor(UNPRESSEDFG); - dc.pressed[ColorBG] = getcolor(PRESSEDBG); - dc.pressed[ColorFG] = getcolor(PRESSEDFG); - dc.decoration[ColorBG] = getcolor(DECORATIONBG); - dc.decoration[ColorFG] = getcolor(DECORATIONFG); + getcolor(background, &dc.normal[ColorBG]); + getcolor(foreground, &dc.normal[ColorFG]); + getcolor(selbackground, &dc.selected[ColorBG]); + getcolor(selforeground, &dc.selected[ColorFG]); + getcolor(separator, &dc.decoration[ColorBG]); + getcolor(border, &dc.decoration[ColorFG]); /* try to get font */ - if ((dc.font = XLoadQueryFont(dpy, FONT)) == NULL) + if ((dc.font = XftFontOpenName(dpy, screen, font)) == NULL) errx(1, "cannot load font"); - dc.fonth = dc.font->ascent + dc.font->descent; - /* create GC and set its font */ + /* create GC */ dc.gc = XCreateGC(dpy, rootwin, 0, NULL); - XSetFont(dpy, dc.gc, dc.font->fid); } /* init menu geometry values */ static void setupgeom(void) { - geom.itemb = ITEMB; - geom.itemh = dc.fonth + ITEMB * 2; - geom.itemw = ITEMW; - geom.border = BORDER; - geom.separator = SEPARATOR; + geom.itemb = itemborder; + geom.itemh = dc.font->height + itemborder * 2; + geom.itemw = width; + geom.border = menuborder; + geom.separator = separatorsize; } /* grab pointer */ static void setupgrab(void) { - XGrabPointer(dpy, rootwin, True, ButtonPressMask | ButtonReleaseMask, - GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + if (XGrabPointer(dpy, rootwin, True, ButtonPressMask, + GrabModeAsync, GrabModeAsync, None, + None, CurrentTime) != GrabSuccess) + errx(1, "cannot grab pointer"); + if (XGrabKeyboard(dpy, rootwin, True, GrabModeAsync, + GrabModeAsync, CurrentTime) != GrabSuccess) + errx(1, "cannot grab keyboard"); } /* allocate an item */ @@ -252,8 +310,8 @@ allocmenu(struct Menu *parent, struct Item *list, unsigned level) menu->level = level; swa.override_redirect = override_redirect; - swa.background_pixel = dc.decoration[ColorBG]; - swa.border_pixel = dc.decoration[ColorFG]; + swa.background_pixel = dc.decoration[ColorBG].pixel; + swa.border_pixel = dc.decoration[ColorFG].pixel; swa.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | LeaveWindowMask; menu->win = XCreateWindow(dpy, rootwin, 0, 0, geom.itemw, geom.itemh, geom.border, @@ -311,6 +369,8 @@ parsestdin(void) rootmenu = menu; prevmenu = menu; count = 1; + curritem->prev = NULL; + curritem->next = NULL; } else if (level < prevmenu->level) { /* item is continuation of a parent menu*/ for (menu = prevmenu, i = level; menu != NULL && i < prevmenu->level; @@ -324,11 +384,19 @@ parsestdin(void) ; item->next = curritem; + + curritem->prev = item; + curritem->next = NULL; + prevmenu = menu; } else if (level == prevmenu->level) { /* item is a continuation of current menu */ for (item = prevmenu->list; item->next != NULL; item = item->next) ; item->next = curritem; + + curritem->prev = item; + curritem->next = NULL; + } else if (level > prevmenu->level) { /* item begins a new menu */ menu = allocmenu(prevmenu, curritem, level); @@ -338,6 +406,9 @@ parsestdin(void) item->submenu = menu; menu->caller = item; + curritem->prev = NULL; + curritem->next = NULL; + prevmenu = menu; } count++; @@ -363,6 +434,7 @@ calcmenu(struct Menu *menu) { XWindowChanges changes; XSizeHints sizeh; + XGlyphInfo ext; struct Item *item; int labelwidth; @@ -375,7 +447,9 @@ calcmenu(struct Menu *menu) else menu->h += geom.itemh; - labelwidth = XTextWidth(dc.font, item->label, item->labellen) + dc.fonth * 2; + XftTextExtentsUtf8(dpy, dc.font, (XftChar8 *)item->label, + item->labellen, &ext); + labelwidth = ext.xOff + dc.font->height * 2; menu->w = MAX(menu->w, labelwidth); } @@ -396,10 +470,10 @@ calcmenu(struct Menu *menu) for (item = menu->parent->list; item->submenu != menu; item = item->next) ; - if (screengeom.screenw - (menu->parent->x + menu->parent->w) >= menu->w) - menu->x = menu->parent->x + menu->parent->w; - else if (menu->parent->x > menu->w) - menu->x = menu->parent->x - menu->w; + if (screengeom.screenw - (menu->parent->x + menu->parent->w + geom.border) >= menu->w) + menu->x = menu->parent->x + menu->parent->w + geom.border; + else if (menu->parent->x > menu->w + geom.border) + menu->x = menu->parent->x - menu->w - geom.border; if (screengeom.screenh - (item->y + menu->parent->y) > menu->h) menu->y = item->y + menu->parent->y; @@ -422,9 +496,10 @@ calcmenu(struct Menu *menu) sizeh.min_height = sizeh.max_height = menu->h; XSetWMNormalHints(dpy, menu->win, &sizeh); - /* create pixmap */ + /* create pixmap and XftDraw */ menu->pixmap = XCreatePixmap(dpy, menu->win, menu->w, menu->h, DefaultDepth(dpy, screen)); + menu->draw = XftDrawCreate(dpy, menu->pixmap, visual, colormap); /* calculate positions of submenus */ for (item = menu->list; item != NULL; item = item->next) { @@ -518,38 +593,37 @@ drawmenu(void) for (menu = currmenu; menu != NULL; menu = menu->parent) { for (item = menu->list; item != NULL; item = item->next) { - unsigned long *color; + XftColor *color; int labelx, labely; /* determine item color */ - if (item->label == NULL) - color = dc.decoration; - else if (item == menu->selected) - color = dc.pressed; + if (item == menu->selected) + color = dc.selected; else - color = dc.unpressed; + color = dc.normal; + + /* continue if item is a separator */ + if (item->label == NULL) + continue; /* draw item box */ - XSetForeground(dpy, dc.gc, color[ColorBG]); + XSetForeground(dpy, dc.gc, color[ColorBG].pixel); XDrawRectangle(dpy, menu->pixmap, dc.gc, 0, item->y, menu->w, item->h); XFillRectangle(dpy, menu->pixmap, dc.gc, 0, item->y, menu->w, item->h); - /* continue if item is a separator */ - if (item->label == NULL) - continue; - /* draw item label */ - labelx = 0 + dc.fonth; - labely = item->y + dc.fonth + geom.itemb; - XSetForeground(dpy, dc.gc, color[ColorFG]); - XDrawString(dpy, menu->pixmap, dc.gc, labelx, labely, - item->label, item->labellen); + labelx = 0 + dc.font->height; + labely = item->y + dc.font->height + geom.itemb / 2; + XSetForeground(dpy, dc.gc, color[ColorFG].pixel); + XftDrawStringUtf8(menu->draw, &color[ColorFG], dc.font, + labelx, labely, item->label, + item->labellen); /* draw triangle, if item contains a submenu */ if (item->submenu != NULL) { - int trianglex = menu->w - dc.fonth + geom.itemb - 1; + int trianglex = menu->w - dc.font->height + geom.itemb - 1; int triangley = item->y + (3 * item->h)/8 -1; XPoint triangle[] = { @@ -569,6 +643,47 @@ drawmenu(void) } } +/* cycle through the items; non-zero direction is next, zero is prev */ +static struct Item * +itemcycle(int direction) +{ + struct Item *item; + struct Item *lastitem; + + item = NULL; + + if (direction == ITEMNEXT) { + if (currmenu->selected == NULL) + item = currmenu->list; + else if (currmenu->selected->next != NULL) + item = currmenu->selected->next; + + while (item != NULL && item->label == NULL) + item = item->next; + + if (item == NULL) + item = currmenu->list; + } else { + for (lastitem = currmenu->list; + lastitem != NULL && lastitem->next != NULL; + lastitem = lastitem->next) + ; + + if (currmenu->selected == NULL) + item = lastitem; + else if (currmenu->selected->prev != NULL) + item = currmenu->selected->prev; + + while (item != NULL && item->label == NULL) + item = item->prev; + + if (item == NULL) + item = lastitem; + } + + return item; +} + /* run event loop */ static void run(void) @@ -576,12 +691,14 @@ run(void) struct Menu *menu; struct Item *item; struct Item *previtem = NULL; + KeySym ksym; XEvent ev; while (!XNextEvent(dpy, &ev)) { switch(ev.type) { case Expose: - drawmenu(); + if (ev.xexpose.count == 0) + drawmenu(); break; case MotionNotify: getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item); @@ -602,6 +719,7 @@ run(void) case ButtonRelease: getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item); if (menu != NULL && item != NULL) { +selectitem: if (item->label == NULL) break; /* ignore separators */ if (item->submenu != NULL) { @@ -610,10 +728,45 @@ run(void) printf("%s\n", item->output); return; } + currmenu->selected = currmenu->list; drawmenu(); + break; } else { return; } + case ButtonPress: + getmenuitem(ev.xbutton.window, ev.xbutton.y, &menu, &item); + if (menu == NULL || item == NULL) + return; + break; + case KeyPress: + ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0); + + if (ksym == XK_Escape && currmenu == rootmenu) + return; + + /* Shift-Tab = ISO_Left_Tab */ + if (ksym == XK_Tab && (ev.xkey.state & ShiftMask)) + ksym = XK_ISO_Left_Tab; + + /* cycle through menu */ + item = NULL; + if (ksym == XK_ISO_Left_Tab || ksym == XK_Up) { + item = itemcycle(ITEMPREV); + } else if (ksym == XK_Tab || ksym == XK_Down) { + item = itemcycle(ITEMNEXT); + } else if ((ksym == XK_Return || ksym == XK_Right) && + currmenu->selected != NULL) { + item = currmenu->selected; + goto selectitem; + } else if ((ksym == XK_Escape || ksym == XK_Left) && + currmenu->parent != NULL) { + item = currmenu->parent->selected; + setcurrmenu(currmenu->parent); + } else + break; + currmenu->selected = item; + drawmenu(); break; case LeaveNotify: currmenu->selected = NULL; @@ -634,6 +787,7 @@ freewindow(struct Menu *menu) freewindow(item->submenu); XFreePixmap(dpy, menu->pixmap); + XftDrawDestroy(menu->draw); XDestroyWindow(dpy, menu->win); } @@ -642,7 +796,14 @@ static void cleanup(void) { freewindow(rootmenu); - XFreeFont(dpy, dc.font); + + XftColorFree(dpy, visual, colormap, &dc.normal[ColorBG]); + XftColorFree(dpy, visual, colormap, &dc.normal[ColorFG]); + XftColorFree(dpy, visual, colormap, &dc.selected[ColorBG]); + XftColorFree(dpy, visual, colormap, &dc.selected[ColorFG]); + XftColorFree(dpy, visual, colormap, &dc.decoration[ColorBG]); + XftColorFree(dpy, visual, colormap, &dc.decoration[ColorFG]); + XFreeGC(dpy, dc.gc); XCloseDisplay(dpy); }