#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
+#include <X11/XKBlib.h>
+#include <X11/Xft/Xft.h>
+
+#define ITEMPREV 0
+#define ITEMNEXT 1
/* macros */
#define LEN(x) (sizeof (x) / sizeof (x[0]))
/* 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 */
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 */
};
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);
/* X variables */
static Colormap colormap;
static Display *dpy;
+static Visual *visual;
static Window rootwin;
static int screen;
static struct DC dc;
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);
getresources();
setupdc();
setupgeom();
- setupgrab();
+ if (override_redirect)
+ setupgrab();
/* generate menus and recalculate them */
parsestdin();
}
/* 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 */
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 */
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;
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 */
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,
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;
;
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);
item->submenu = menu;
menu->caller = item;
+ curritem->prev = NULL;
+ curritem->next = NULL;
+
prevmenu = menu;
}
count++;
{
XWindowChanges changes;
XSizeHints sizeh;
+ XGlyphInfo ext;
struct Item *item;
int labelwidth;
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);
}
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) {
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 */
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[] = {
}
}
+/* 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)
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);
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) {
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;
freewindow(item->submenu);
XFreePixmap(dpy, menu->pixmap);
+ XftDrawDestroy(menu->draw);
XDestroyWindow(dpy, menu->win);
}
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);
}