X-Git-Url: https://git.subgeniuskitty.com/xmenu/.git/blobdiff_plain/f644b8bc229efaa87ac1d441918496e7e24cf29c..697f63c47427bcca9115e74859ce4debe788fdc5:/xmenu.c diff --git a/xmenu.c b/xmenu.c index 0afaaa9..82617d7 100644 --- a/xmenu.c +++ b/xmenu.c @@ -6,6 +6,11 @@ #include #include #include +#include +#include + +#define ITEMPREV 0 +#define ITEMNEXT 1 /* macros */ #define LEN(x) (sizeof (x) / sizeof (x[0])) @@ -17,14 +22,13 @@ enum {ColorFG, ColorBG, ColorLast}; /* draw context structure */ struct DC { - unsigned long normal[ColorLast]; - unsigned long selected[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 */ @@ -49,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 */ }; @@ -62,11 +67,12 @@ 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); @@ -87,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; @@ -126,6 +133,7 @@ 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); @@ -133,7 +141,8 @@ main(int argc, char *argv[]) getresources(); setupdc(); setupgeom(); - setupgrab(); + if (override_redirect) + setupgrab(); /* generate menus and recalculate them */ parsestdin(); @@ -201,14 +210,11 @@ getresources(void) } /* get color from color string */ -static unsigned long -getcolor(const char *s) +static void +getcolor(const char *s, XftColor *color) { - XColor color; - - if(!XAllocNamedColor(dpy, colormap, s, &color, &color)) + if(!XftColorAllocName(dpy, visual, colormap, s, color)) errx(1, "cannot allocate color: %s", s); - return color.pixel; } /* init draw context */ @@ -216,21 +222,19 @@ static void setupdc(void) { /* get color pixels */ - dc.normal[ColorBG] = getcolor(background); - dc.normal[ColorFG] = getcolor(foreground); - dc.selected[ColorBG] = getcolor(selbackground); - dc.selected[ColorFG] = getcolor(selforeground); - dc.decoration[ColorBG] = getcolor(separator); - dc.decoration[ColorFG] = getcolor(border); + 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 */ @@ -238,7 +242,7 @@ static void setupgeom(void) { geom.itemb = itemborder; - geom.itemh = dc.fonth + itemborder * 2; + geom.itemh = dc.font->height + itemborder * 2; geom.itemw = width; geom.border = menuborder; geom.separator = separatorsize; @@ -248,8 +252,13 @@ setupgeom(void) 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 */ @@ -301,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, @@ -360,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; @@ -373,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); @@ -387,6 +406,9 @@ parsestdin(void) item->submenu = menu; menu->caller = item; + curritem->prev = NULL; + curritem->next = NULL; + prevmenu = menu; } count++; @@ -412,6 +434,7 @@ calcmenu(struct Menu *menu) { XWindowChanges changes; XSizeHints sizeh; + XGlyphInfo ext; struct Item *item; int labelwidth; @@ -424,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); } @@ -471,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) { @@ -567,7 +593,7 @@ 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 */ @@ -581,22 +607,23 @@ drawmenu(void) 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); /* 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[] = { @@ -616,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) @@ -623,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); @@ -649,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) { @@ -657,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; @@ -681,6 +787,7 @@ freewindow(struct Menu *menu) freewindow(item->submenu); XFreePixmap(dpy, menu->pixmap); + XftDrawDestroy(menu->draw); XDestroyWindow(dpy, menu->win); } @@ -689,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); }