#include <Imlib2.h>
#include "xmenu.h"
-/*
- * Function declarations
- */
-
-/* argument parser */
-static void parseposition(char *optarg);
-
-/* initializers, and their helper routines */
-static void parsefonts(const char *s);
-static void ealloccolor(const char *s, XftColor *color);
-static void initmonitor(void);
-static void initresources(void);
-static void initdc(void);
-static void initiconsize(void);
-static void initatoms(void);
-
-/* structure builders, and their helper routines */
-static struct Item *allocitem(const char *label, const char *output, char *file);
-static struct Menu *allocmenu(struct Menu *parent, struct Item *list, unsigned level);
-static struct Menu *buildmenutree(unsigned level, const char *label, const char *output, char *file);
-static struct Menu *parsestdin(void);
-
-/* text drawer, and its helper routine */
-static FcChar32 getnextutf8char(const char *s, const char **end_ret);
-static XftFont *getfontucode(FcChar32 ucode);
-static int drawtext(XftDraw *draw, XftColor *color, int x, int y, unsigned h, const char *text);
-
-/* structure setters, and their helper routines */
-static void setupitems(struct Menu *menu);
-static void setupmenupos(struct Menu *menu);
-static void setupmenu(struct Menu *menu, XClassHint *classh);
-
-/* grabbers */
-static void grabpointer(void);
-static void grabkeyboard(void);
-
-/* item drawer, and its helper routine */
-static Imlib_Image loadicon(const char *file);
-static void drawitems(struct Menu *menu);
-
-/* menu drawers and mappers */
-static void drawmenus(struct Menu *currmenu);
-static void mapmenu(struct Menu *currmenu);
-
-/* getters */
-static struct Menu *getmenu(struct Menu *currmenu, Window win);
-static struct Item *getitem(struct Menu *menu, int y);
-
-/* cycle through items */
-static struct Item *itemcycle(struct Menu *currmenu, int direction);
-
-/* main event loop */
-static void run(struct Menu *currmenu);
-
-/* cleaners */
-static void cleanmenu(struct Menu *menu);
-static void cleanup(void);
-
-/* show usage */
-static void usage(void);
-
-
-/*
- * Variable declarations
- */
-
/* X stuff */
static Display *dpy;
static int screen;
static Visual *visual;
static Window rootwin;
static Colormap colormap;
+static XrmDatabase xdb;
+static char *xrm;
static struct DC dc;
static struct Monitor mon;
static Atom utf8string;
/* flags */
static int iflag = 0; /* whether to disable icons */
+static int rflag = 0; /* whether to disable right-click */
static int mflag = 0; /* whether the user specified a monitor with -p */
static int pflag = 0; /* whether the user specified a position with -p */
static int wflag = 0; /* whether to let the window manager control XMenu */
/* include config variable */
#include "config.h"
-
-/*
- * Function implementations
- */
-
-/* xmenu: generate menu from stdin and print selected entry to stdout */
-int
-main(int argc, char *argv[])
+/* show usage */
+static void
+usage(void)
{
- struct Menu *rootmenu;
- XClassHint classh;
- int ch;
-
- while ((ch = getopt(argc, argv, "ip:w")) != -1) {
- switch (ch) {
- case 'i':
- iflag = 1;
- break;
- case 'p':
- pflag = 1;
- parseposition(optarg);
- break;
- case 'w':
- wflag = 1;
- break;
- default:
- usage();
- break;
- }
- }
- argc -= optind;
- argv += optind;
-
- if (argc > 1)
- usage();
-
- /* open connection to server and set X variables */
- if ((dpy = XOpenDisplay(NULL)) == NULL)
- errx(1, "could not open display");
- screen = DefaultScreen(dpy);
- visual = DefaultVisual(dpy, screen);
- rootwin = RootWindow(dpy, screen);
- colormap = DefaultColormap(dpy, screen);
-
- /* imlib2 stuff */
- if (!iflag) {
- imlib_set_cache_size(2048 * 1024);
- imlib_context_set_dither(1);
- imlib_context_set_display(dpy);
- imlib_context_set_visual(visual);
- imlib_context_set_colormap(colormap);
- }
-
- /* initializers */
- initmonitor();
- initresources();
- initdc();
- initiconsize();
- initatoms();
-
- /* set window class */
- classh.res_class = PROGNAME;
- if (argc == 1)
- classh.res_name = *argv;
- else
- classh.res_name = PROGNAME;
-
- /* generate menus and set them up */
- rootmenu = parsestdin();
- if (rootmenu == NULL)
- errx(1, "no menu generated");
- setupmenu(rootmenu, &classh);
-
- /* grab mouse and keyboard */
- if (!wflag) {
- grabpointer();
- grabkeyboard();
- }
-
- /* run event loop */
- run(rootmenu);
-
- /* freeing stuff */
- cleanmenu(rootmenu);
- cleanup();
-
- return 0;
+ (void)fprintf(stderr, "usage: xmenu [-irw] [-p position] [title]\n");
+ exit(1);
}
/* parse position string from -p,
errx(1, "improper position: %s", optarg);
}
-/* parse color string */
+/* get configuration from X resources */
+static void
+getresources(void)
+{
+ char *type;
+ XrmValue xval;
+
+ if (xrm == NULL || xdb == NULL)
+ return;
+
+ if (XrmGetResource(xdb, "xmenu.borderWidth", "*", &type, &xval) == True)
+ GETNUM(config.border_pixels, xval.addr)
+ if (XrmGetResource(xdb, "xmenu.separatorWidth", "*", &type, &xval) == True)
+ GETNUM(config.separator_pixels, xval.addr)
+ if (XrmGetResource(xdb, "xmenu.height", "*", &type, &xval) == True)
+ GETNUM(config.height_pixels, xval.addr)
+ if (XrmGetResource(xdb, "xmenu.width", "*", &type, &xval) == True)
+ GETNUM(config.width_pixels, xval.addr)
+ if (XrmGetResource(xdb, "xmenu.gap", "*", &type, &xval) == True)
+ GETNUM(config.gap_pixels, xval.addr)
+ if (XrmGetResource(xdb, "xmenu.background", "*", &type, &xval) == True)
+ config.background_color = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.foreground", "*", &type, &xval) == True)
+ config.foreground_color = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.selbackground", "*", &type, &xval) == True)
+ config.selbackground_color = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.selforeground", "*", &type, &xval) == True)
+ config.selforeground_color = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.separator", "*", &type, &xval) == True)
+ config.separator_color = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.border", "*", &type, &xval) == True)
+ config.border_color = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.font", "*", &type, &xval) == True)
+ config.font = xval.addr;
+ if (XrmGetResource(xdb, "xmenu.alignment", "*", &type, &xval) == True) {
+ if (strcasecmp(xval.addr, "center") == 0)
+ config.alignment = CenterAlignment;
+ else if (strcasecmp(xval.addr, "left") == 0)
+ config.alignment = LeftAlignment;
+ else if (strcasecmp(xval.addr, "right") == 0)
+ config.alignment = RightAlignment;
+ }
+}
+
+/* get configuration from command-line options */
+static char *
+getoptions(int argc, char *argv[])
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "ip:rw")) != -1) {
+ switch (ch) {
+ case 'i':
+ iflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ parseposition(optarg);
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc > 1)
+ usage();
+ else if (argc == 1)
+ return *argv;
+ return PROGNAME;
+}
+
+/* parse font string */
static void
parsefonts(const char *s)
{
}
}
-/* read xrdb for configuration options */
-static void
-initresources(void)
-{
- long n;
- char *type;
- char *xrm;
- XrmDatabase xdb;
- XrmValue xval;
-
- XrmInitialize();
- if ((xrm = XResourceManagerString(dpy)) == NULL)
- return;
-
- xdb = XrmGetStringDatabase(xrm);
-
- if (XrmGetResource(xdb, "xmenu.borderWidth", "*", &type, &xval) == True)
- if ((n = strtol(xval.addr, NULL, 10)) > 0)
- config.border_pixels = n;
- if (XrmGetResource(xdb, "xmenu.separatorWidth", "*", &type, &xval) == True)
- if ((n = strtol(xval.addr, NULL, 10)) > 0)
- config.separator_pixels = n;
- if (XrmGetResource(xdb, "xmenu.height", "*", &type, &xval) == True)
- if ((n = strtol(xval.addr, NULL, 10)) > 0)
- config.height_pixels = n;
- if (XrmGetResource(xdb, "xmenu.width", "*", &type, &xval) == True)
- if ((n = strtol(xval.addr, NULL, 10)) > 0)
- config.width_pixels = n;
- if (XrmGetResource(xdb, "xmenu.gap", "*", &type, &xval) == True)
- if ((n = strtol(xval.addr, NULL, 10)) > 0)
- config.gap_pixels = n;
- if (XrmGetResource(xdb, "xmenu.background", "*", &type, &xval) == True)
- config.background_color = strdup(xval.addr);
- if (XrmGetResource(xdb, "xmenu.foreground", "*", &type, &xval) == True)
- config.foreground_color = strdup(xval.addr);
- if (XrmGetResource(xdb, "xmenu.selbackground", "*", &type, &xval) == True)
- config.selbackground_color = strdup(xval.addr);
- if (XrmGetResource(xdb, "xmenu.selforeground", "*", &type, &xval) == True)
- config.selforeground_color = strdup(xval.addr);
- if (XrmGetResource(xdb, "xmenu.separator", "*", &type, &xval) == True)
- config.separator_color = strdup(xval.addr);
- if (XrmGetResource(xdb, "xmenu.border", "*", &type, &xval) == True)
- config.border_color = strdup(xval.addr);
- if (XrmGetResource(xdb, "xmenu.font", "*", &type, &xval) == True)
- config.font = strdup(xval.addr);
-
- XrmDestroyDatabase(xdb);
-}
-
/* init draw context */
static void
initdc(void)
setupitems(struct Menu *menu)
{
struct Item *item;
+ int itemwidth;
menu->w = config.width_pixels;
+ menu->maxtextw = 0;
for (item = menu->list; item != NULL; item = item->next) {
- int itemwidth;
- int textwidth;
-
item->y = menu->h;
-
if (item->label == NULL) /* height for separator item */
item->h = config.separator_pixels;
else
item->h = config.height_pixels;
menu->h += item->h;
-
if (item->label)
- textwidth = drawtext(NULL, NULL, 0, 0, 0, item->label);
+ item->textw = drawtext(NULL, NULL, 0, 0, 0, item->label);
else
- textwidth = 0;
+ item->textw = 0;
/*
* set menu width
*
- * the item width depends on the size of its label (textwidth),
+ * the item width depends on the size of its label (item->textw),
* and it is only used to calculate the width of the menu (which
* is equal to the width of the largest item).
*
* if the iflag is set (icons are disabled) then the horizontal
* padding appears 3 times: before the label and around the triangle.
*/
- itemwidth = textwidth + config.triangle_width + config.horzpadding * 3;
+ itemwidth = item->textw + config.triangle_width + config.horzpadding * 3;
itemwidth += (iflag || !menu->hasicon) ? 0 : config.iconsize + config.horzpadding;
menu->w = MAX(menu->w, itemwidth);
+ menu->maxtextw = MAX(menu->maxtextw, item->textw);
}
}
width = menu->w + config.border_pixels * 2;
height = menu->h + config.border_pixels * 2;
if (menu->parent == NULL) { /* if root menu, calculate in respect to cursor */
- if (pflag || (config.posx > mon.x && mon.x + mon.w - config.posx >= width))
+ if (pflag || (config.posx >= mon.x && mon.x + mon.w - config.posx >= width))
menu->x = config.posx;
else if (config.posx > width)
menu->x = config.posx - width;
- if (pflag || (config.posy > mon.y && mon.y + mon.h - config.posy >= height))
+ if (pflag || (config.posy >= mon.y && mon.y + mon.h - config.posy >= height))
menu->y = config.posy;
else if (mon.y + mon.h > height)
menu->y = mon.y + mon.h - height;
errx(1, "could not grab keyboard");
}
+/* ungrab pointer and keyboard */
+static void
+ungrab(void)
+{
+ XUngrabPointer(dpy, CurrentTime);
+ XUngrabKeyboard(dpy, CurrentTime);
+}
+
/* load and scale icon */
static Imlib_Image
loadicon(const char *file)
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) {
- XftDraw *dsel, *dunsel;
- int x, y;
-
item->unsel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
DefaultDepth(dpy, screen));
item->sel = item->unsel;
} else {
-
item->sel = XCreatePixmap(dpy, menu->win, menu->w, item->h,
DefaultDepth(dpy, screen));
XSetForeground(dpy, dc.gc, dc.selected[ColorBG].pixel);
XFillRectangle(dpy, item->sel, dc.gc, 0, 0, menu->w, item->h);
/* draw text */
- x = config.horzpadding;
- x += (iflag || !menu->hasicon) ? 0 : config.horzpadding + config.iconsize;
+ 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], x, 0, item->h, item->label);
+ drawtext(dsel, &dc.selected[ColorFG], textx, 0, item->h, item->label);
XSetForeground(dpy, dc.gc, dc.normal[ColorFG].pixel);
- drawtext(dunsel, &dc.normal[ColorFG], x, 0, item->h, item->label);
+ drawtext(dunsel, &dc.normal[ColorFG], textx, 0, item->h, item->label);
XftDrawDestroy(dsel);
XftDrawDestroy(dunsel);
static struct Item *
itemcycle(struct Menu *currmenu, int direction)
{
- struct Item *item;
+ struct Item *item = NULL;
struct Item *lastitem;
- item = NULL;
+ for (lastitem = currmenu->list; lastitem && lastitem->next; lastitem = lastitem->next)
+ ;
- if (direction == ITEMNEXT) {
+ /* select item (either separator or labeled item) in given direction */
+ switch (direction) {
+ case 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)
- ;
-
+ break;
+ case ITEMPREV:
if (currmenu->selected == NULL)
item = lastitem;
else if (currmenu->selected->prev != NULL)
item = currmenu->selected->prev;
+ break;
+ case ITEMFIRST:
+ item = currmenu->list;
+ break;
+ case ITEMLAST:
+ item = lastitem;
+ break;
+ }
+ /*
+ * the selected item can be a separator
+ * let's select the closest labeled item (ie., one that isn't a separator)
+ */
+ switch (direction) {
+ case ITEMNEXT:
+ case ITEMFIRST:
+ while (item != NULL && item->label == NULL)
+ item = item->next;
+ if (item == NULL)
+ item = currmenu->list;
+ break;
+ case ITEMPREV:
+ case ITEMLAST:
while (item != NULL && item->label == NULL)
item = item->prev;
-
if (item == NULL)
item = lastitem;
+ break;
}
return item;
}
+/* check if button is used to open a item on click */
+static int
+isclickbutton(unsigned int button)
+{
+ if (button == Button1)
+ return 1;
+ if (!rflag && button == Button3)
+ return 1;
+ return 0;
+}
+
/* run event loop */
static void
run(struct Menu *currmenu)
struct Menu *menu;
struct Item *item;
struct Item *previtem = NULL;
+ struct Item *lastitem;
KeySym ksym;
XEvent ev;
drawmenus(currmenu);
break;
case ButtonRelease:
+ if (!isclickbutton(ev.xbutton.button))
+ break;
menu = getmenu(currmenu, ev.xbutton.window);
item = getitem(menu, ev.xbutton.y);
if (menu == NULL || item == NULL)
/* cycle through menu */
item = NULL;
- if (ksym == XK_ISO_Left_Tab || ksym == XK_Up) {
+ if (ksym == XK_Home || ksym == KSYMFIRST) {
+ item = itemcycle(currmenu, ITEMFIRST);
+ } else if (ksym == XK_End || ksym == KSYMLAST) {
+ item = itemcycle(currmenu, ITEMLAST);
+ } else if (ksym == XK_ISO_Left_Tab || ksym == XK_Up || ksym == KSYMUP) {
item = itemcycle(currmenu, ITEMPREV);
- } else if (ksym == XK_Tab || ksym == XK_Down) {
+ } else if (ksym == XK_Tab || ksym == XK_Down || ksym == KSYMDOWN) {
item = itemcycle(currmenu, ITEMNEXT);
- } else if ((ksym == XK_Return || ksym == XK_Right) &&
- currmenu->selected != NULL) {
+ } else if (ksym >= XK_1 && ksym <= XK_9){
+ item = itemcycle(currmenu, ITEMFIRST);
+ lastitem = itemcycle(currmenu, ITEMLAST);
+ for (int i = ksym - XK_1; i > 0 && item != lastitem; i--) {
+ currmenu->selected = item;
+ item = itemcycle(currmenu, ITEMNEXT);
+ }
+ } else if ((ksym == XK_Return || ksym == XK_Right || ksym == KSYMRIGHT) &&
+ currmenu->selected != NULL) {
item = currmenu->selected;
goto selectitem;
- } else if ((ksym == XK_Escape || ksym == XK_Left) &&
+ } else if ((ksym == XK_Escape || ksym == XK_Left || ksym == KSYMLEFT) &&
currmenu->parent != NULL) {
item = currmenu->parent->selected;
currmenu = currmenu->parent;
free(menu);
}
-/* cleanup X and exit */
+/* cleanup draw context */
static void
-cleanup(void)
+cleandc(void)
{
size_t i;
- XUngrabPointer(dpy, CurrentTime);
- XUngrabKeyboard(dpy, CurrentTime);
-
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.separator);
XftColorFree(dpy, visual, colormap, &dc.border);
-
for (i = 0; i < dc.nfonts; i++)
XftFontClose(dpy, dc.fonts[i]);
-
XFreeGC(dpy, dc.gc);
- XCloseDisplay(dpy);
}
-/* show usage */
-static void
-usage(void)
+/* xmenu: generate menu from stdin and print selected entry to stdout */
+int
+main(int argc, char *argv[])
{
- (void)fprintf(stderr, "usage: xmenu [-iw] [-p position] [title]\n");
- exit(1);
+ struct Menu *rootmenu;
+ XClassHint classh;
+
+ /* open connection to server and set X variables */
+ if ((dpy = XOpenDisplay(NULL)) == NULL)
+ errx(1, "could not open display");
+ screen = DefaultScreen(dpy);
+ visual = DefaultVisual(dpy, screen);
+ rootwin = RootWindow(dpy, screen);
+ colormap = DefaultColormap(dpy, screen);
+ XrmInitialize();
+ if ((xrm = XResourceManagerString(dpy)) != NULL)
+ xdb = XrmGetStringDatabase(xrm);
+
+ /* get configuration */
+ getresources();
+ classh.res_class = PROGNAME;
+ classh.res_name = getoptions(argc, argv);
+
+ /* imlib2 stuff */
+ if (!iflag) {
+ imlib_set_cache_size(2048 * 1024);
+ imlib_context_set_dither(1);
+ imlib_context_set_display(dpy);
+ imlib_context_set_visual(visual);
+ imlib_context_set_colormap(colormap);
+ }
+
+ /* initializers */
+ initmonitor();
+ initdc();
+ initiconsize();
+ initatoms();
+
+ /* generate menus and set them up */
+ rootmenu = parsestdin();
+ if (rootmenu == NULL)
+ errx(1, "no menu generated");
+ setupmenu(rootmenu, &classh);
+
+ /* grab mouse and keyboard */
+ if (!wflag) {
+ grabpointer();
+ grabkeyboard();
+ }
+
+ /* run event loop */
+ run(rootmenu);
+
+ /* clean stuff */
+ ungrab();
+ cleanmenu(rootmenu);
+ cleandc();
+ XrmDestroyDatabase(xdb);
+ XCloseDisplay(dpy);
+
+ return 0;
}