X-Git-Url: https://git.subgeniuskitty.com/xmenu/.git/blobdiff_plain/73a2a23dbd212da1047baca9982d2ffd4d0254bc..b33886d7a213473ca83262488b4b29861f34d42b:/xmenu.c diff --git a/xmenu.c b/xmenu.c index 51412b3..90e7906 100644 --- a/xmenu.c +++ b/xmenu.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -23,14 +24,18 @@ 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; static Atom wmdelete; static Atom netatom[NetLast]; +static XIM xim; /* 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 */ @@ -42,7 +47,7 @@ static int wflag = 0; /* whether to let the window manager control XMenu */ static void usage(void) { - (void)fprintf(stderr, "usage: xmenu [-iw] [-p position] [title]\n"); + (void)fprintf(stderr, "usage: xmenu [-irw] [-p position] [title]\n"); exit(1); } @@ -86,6 +91,85 @@ error: errx(1, "improper position: %s", optarg); } +/* 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) @@ -182,55 +266,6 @@ initmonitor(void) } } -/* 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) @@ -339,6 +374,11 @@ allocmenu(struct Menu *parent, struct Item *list, unsigned level) CWBorderPixel | CWEventMask | CWSaveUnder, &swa); + menu->xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, menu->win, XNFocusWindow, menu->win, NULL); + if (menu->xic == NULL) + errx(1, "XCreateIC: could not obtain input method"); + return menu; } @@ -582,29 +622,26 @@ static 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). * @@ -613,9 +650,10 @@ setupitems(struct Menu *menu) * 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); } } @@ -628,12 +666,12 @@ setupmenupos(struct Menu *menu) 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; @@ -738,6 +776,32 @@ grabkeyboard(void) errx(1, "could not grab keyboard"); } +/* try to grab focus, we may have to wait for another process to ungrab */ +static void +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); +} + /* load and scale icon */ static Imlib_Image loadicon(const char *file) @@ -810,12 +874,12 @@ 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)); @@ -830,21 +894,30 @@ drawitems(struct Menu *menu) 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); @@ -968,6 +1041,7 @@ mapmenu(struct Menu *currmenu) } prevmenu = currmenu; + grabfocus(currmenu->win); } /* get menu of given window */ @@ -1055,24 +1129,77 @@ itemcycle(struct Menu *currmenu, int direction) 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; +} + +/* append buf into text */ +static int +append(char *text, char *buf, size_t textsize, size_t buflen) +{ + size_t textlen; + + textlen = strlen(text); + if (iscntrl(*buf)) + return 0; + if (textlen + buflen > textsize - 1) + return 0; + if (buflen < 1) + return 0; + memcpy(text + textlen, buf, buflen); + text[textlen + buflen] = '\0'; + return 1; +} + +/* get item in menu matching text */ +static struct Item * +matchitem(struct Menu *menu, char *text) +{ + struct Item *item; + char *s; + size_t textlen; + + textlen = strlen(text); + for (item = menu->list; item; item = item->next) + for (s = item->label; s && *s; s++) + if (strncasecmp(s, text, textlen) == 0) + return item; + return NULL; +} + /* run event loop */ static void run(struct Menu *currmenu) { + char text[BUFSIZ]; + char buf[32]; struct Menu *menu; struct Item *item; struct Item *previtem = NULL; - struct Item *lastitem; + struct Item *lastitem, *select; KeySym ksym; + Status status; XEvent ev; + int action; + int len; + text[0] = '\0'; mapmenu(currmenu); - while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, None)) + continue; + action = ACTION_NOP; switch(ev.type) { case Expose: if (ev.xexpose.count == 0) - drawmenus(currmenu); + action = ACTION_DRAW; break; case MotionNotify: menu = getmenu(currmenu, ev.xbutton.window); @@ -1080,22 +1207,23 @@ run(struct Menu *currmenu) if (menu == NULL || item == NULL || previtem == item) break; previtem = item; - menu->selected = item; + select = menu->selected = item; if (item->submenu != NULL) { currmenu = item->submenu; - currmenu->selected = NULL; + select = NULL; } else { currmenu = menu; } - mapmenu(currmenu); - drawmenus(currmenu); + action = ACTION_SELECT | ACTION_MAP | ACTION_DRAW; 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) break; -selectitem: +enteritem: if (item->label == NULL) break; /* ignore separators */ if (item->submenu != NULL) { @@ -1104,9 +1232,8 @@ selectitem: printf("%s\n", item->output); return; } - mapmenu(currmenu); - currmenu->selected = currmenu->list; - drawmenus(currmenu); + select = currmenu->list; + action = ACTION_SELECT | ACTION_MAP | ACTION_DRAW; break; case ButtonPress: menu = getmenu(currmenu, ev.xbutton.window); @@ -1114,7 +1241,16 @@ selectitem: return; break; case KeyPress: - ksym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0, 0); + len = XmbLookupString(currmenu->xic, &ev.xkey, buf, sizeof buf, &ksym, &status); + switch(status) { + default: /* XLookupNone, XBufferOverflow */ + continue; + case XLookupChars: + goto append; + case XLookupKeySym: /* FALLTHROUGH */ + case XLookupBoth: + break; + } /* esc closes xmenu when current menu is the root menu */ if (ksym == XK_Escape && currmenu->parent == NULL) @@ -1144,21 +1280,30 @@ selectitem: } else if ((ksym == XK_Return || ksym == XK_Right || ksym == KSYMRIGHT) && currmenu->selected != NULL) { item = currmenu->selected; - goto selectitem; + goto enteritem; } else if ((ksym == XK_Escape || ksym == XK_Left || ksym == KSYMLEFT) && currmenu->parent != NULL) { item = currmenu->parent->selected; currmenu = currmenu->parent; - mapmenu(currmenu); - } else + action = ACTION_MAP; + } else { +append: + if (append(text, buf, sizeof text, len)) { + currmenu->selected = matchitem(currmenu, text); + action = ACTION_DRAW; + } else { + select = NULL; + action = ACTION_SELECT | ACTION_DRAW; + } break; - currmenu->selected = item; - drawmenus(currmenu); + } + select = item; + action |= ACTION_SELECT | ACTION_DRAW; break; case LeaveNotify: previtem = NULL; - currmenu->selected = NULL; - drawmenus(currmenu); + select = NULL; + action = ACTION_SELECT | ACTION_DRAW; break; case ConfigureNotify: menu = getmenu(currmenu, ev.xconfigure.window); @@ -1175,9 +1320,17 @@ selectitem: if (menu->parent == NULL) return; /* closing the root menu closes the program */ currmenu = menu->parent; - mapmenu(currmenu); + action = ACTION_MAP; break; } + if (action & ACTION_SELECT) { + currmenu->selected = select; + text[0] = '\0'; + } + if (action & ACTION_MAP) + mapmenu(currmenu); + if (action & ACTION_DRAW) + drawmenus(currmenu); } } @@ -1209,27 +1362,21 @@ cleanmenu(struct Menu *menu) 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); } /* xmenu: generate menu from stdin and print selected entry to stdout */ @@ -1238,38 +1385,26 @@ main(int argc, char *argv[]) { 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 (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + warnx("warning: no locale support"); 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); + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + errx(1, "XOpenIM: could not open input device"); + + /* get configuration */ + getresources(); + classh.res_class = PROGNAME; + classh.res_name = getoptions(argc, argv); /* imlib2 stuff */ if (!iflag) { @@ -1282,18 +1417,10 @@ main(int argc, char *argv[]) /* 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) @@ -1309,9 +1436,12 @@ main(int argc, char *argv[]) /* run event loop */ run(rootmenu); - /* freeing stuff */ + /* clean stuff */ + ungrab(); cleanmenu(rootmenu); - cleanup(); + cleandc(); + XrmDestroyDatabase(xdb); + XCloseDisplay(dpy); return 0; }