garbeam@1: /* garbeam@1: * (C)opyright MMVI Anselm R. Garbe garbeam@1: * (C)opyright MMVI Sander van Dijk garbeam@1: * See LICENSE file for license details. garbeam@1: */ garbeam@1: garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: #include garbeam@1: garbeam@1: #include garbeam@1: #include garbeam@1: garbeam@1: typedef struct Item Item; garbeam@1: garbeam@1: struct Item { garbeam@1: Item *next; /* traverses all items */ garbeam@1: Item *left, *right; /* traverses items matching current search pattern */ garbeam@1: char *text; garbeam@1: }; garbeam@1: garbeam@1: static char *title = nil; garbeam@1: static Bool done = False; garbeam@1: static int ret = 0; garbeam@1: static char text[4096]; garbeam@1: static BlitzColor selcolor; garbeam@1: static BlitzColor normcolor; garbeam@1: static Window win; garbeam@1: static XRectangle mrect; garbeam@1: static Item *allitem = nil; /* first of all items */ garbeam@1: static Item *item = nil; /* first of pattern matching items */ garbeam@1: static Item *sel = nil; garbeam@1: static Item *nextoff = nil; garbeam@1: static Item *prevoff = nil; garbeam@1: static Item *curroff = nil; garbeam@1: static int nitem = 0; garbeam@1: static unsigned int cmdw = 0; garbeam@1: static unsigned int twidth = 0; garbeam@1: static unsigned int cwidth = 0; garbeam@1: static Blitz blz = {0}; garbeam@1: static BlitzBrush brush = {0}; garbeam@1: static const int seek = 30; /* 30px */ garbeam@1: garbeam@1: static void draw_menu(void); garbeam@1: static void handle_kpress(XKeyEvent * e); garbeam@1: garbeam@1: static char version[] = "wmiimenu - " VERSION ", (C)opyright MMIV-MMVI Anselm R. Garbe\n"; garbeam@1: garbeam@1: static void garbeam@1: usage() garbeam@1: { garbeam@1: fprintf(stderr, "%s", "usage: wmiimenu [-v] [-t ]\n"); garbeam@1: exit(1); garbeam@1: } garbeam@1: garbeam@1: static void garbeam@1: update_offsets() garbeam@1: { garbeam@1: unsigned int tw, w = cmdw + 2 * seek; garbeam@1: garbeam@1: if(!curroff) garbeam@1: return; garbeam@1: garbeam@1: for(nextoff = curroff; nextoff; nextoff=nextoff->right) { garbeam@1: tw = blitz_textwidth(brush.font, nextoff->text); garbeam@1: if(tw > mrect.width / 3) garbeam@1: tw = mrect.width / 3; garbeam@1: w += tw + mrect.height; garbeam@1: if(w > mrect.width) garbeam@1: break; garbeam@1: } garbeam@1: garbeam@1: w = cmdw + 2 * seek; garbeam@1: for(prevoff = curroff; prevoff && prevoff->left; prevoff=prevoff->left) { garbeam@1: tw = blitz_textwidth(brush.font, prevoff->left->text); garbeam@1: if(tw > mrect.width / 3) garbeam@1: tw = mrect.width / 3; garbeam@1: w += tw + mrect.height; garbeam@1: if(w > mrect.width) garbeam@1: break; garbeam@1: } garbeam@1: } garbeam@1: garbeam@1: static void garbeam@1: update_items(char *pattern) garbeam@1: { garbeam@1: unsigned int plen = strlen(pattern); garbeam@1: Item *i, *j; garbeam@1: garbeam@1: if(!pattern) garbeam@1: return; garbeam@1: garbeam@1: if(!title || *pattern) garbeam@1: cmdw = cwidth; garbeam@1: else garbeam@1: cmdw = twidth; garbeam@1: garbeam@1: item = j = nil; garbeam@1: nitem = 0; garbeam@1: garbeam@1: for(i = allitem; i; i=i->next) garbeam@1: if(!plen || !strncmp(pattern, i->text, plen)) { garbeam@1: if(!j) garbeam@1: item = i; garbeam@1: else garbeam@1: j->right = i; garbeam@1: i->left = j; garbeam@1: i->right = nil; garbeam@1: j = i; garbeam@1: nitem++; garbeam@1: } garbeam@1: for(i = allitem; i; i=i->next) garbeam@1: if(plen && strncmp(pattern, i->text, plen) garbeam@1: && strstr(i->text, pattern)) { garbeam@1: if(!j) garbeam@1: item = i; garbeam@1: else garbeam@1: j->right = i; garbeam@1: i->left = j; garbeam@1: i->right = nil; garbeam@1: j = i; garbeam@1: nitem++; garbeam@1: } garbeam@1: garbeam@1: curroff = prevoff = nextoff = sel = item; garbeam@1: garbeam@1: update_offsets(); garbeam@1: } garbeam@1: garbeam@1: /* creates brush structs for brush mode drawing */ garbeam@1: static void garbeam@1: draw_menu() garbeam@1: { garbeam@1: unsigned int offx = 0; garbeam@1: garbeam@1: Item *i; garbeam@1: garbeam@1: brush.align = WEST; garbeam@1: garbeam@1: brush.rect = mrect; garbeam@1: brush.rect.x = 0; garbeam@1: brush.rect.y = 0; garbeam@1: brush.color = normcolor; garbeam@1: brush.border = False; garbeam@1: blitz_draw_tile(&brush); garbeam@1: garbeam@1: /* print command */ garbeam@1: if(!title || text[0]) { garbeam@1: brush.color = normcolor; garbeam@1: cmdw = cwidth; garbeam@1: if(cmdw && item) garbeam@1: brush.rect.width = cmdw; garbeam@1: blitz_draw_label(&brush, text); garbeam@1: } garbeam@1: else { garbeam@1: cmdw = twidth; garbeam@1: brush.color = selcolor; garbeam@1: brush.rect.width = cmdw; garbeam@1: blitz_draw_label(&brush, title); garbeam@1: } garbeam@1: offx += brush.rect.width; garbeam@1: garbeam@1: brush.align = CENTER; garbeam@1: if(curroff) { garbeam@1: brush.color = normcolor; garbeam@1: brush.rect.x = offx; garbeam@1: brush.rect.width = seek; garbeam@1: offx += brush.rect.width; garbeam@1: blitz_draw_label(&brush, (curroff && curroff->left) ? "<" : nil); garbeam@1: garbeam@1: /* determine maximum items */ garbeam@1: for(i = curroff; i != nextoff; i=i->right) { garbeam@1: brush.color = normcolor; garbeam@1: brush.border = False; garbeam@1: brush.rect.x = offx; garbeam@1: brush.rect.width = blitz_textwidth(brush.font, i->text); garbeam@1: if(brush.rect.width > mrect.width / 3) garbeam@1: brush.rect.width = mrect.width / 3; garbeam@1: brush.rect.width += mrect.height; garbeam@1: if(sel == i) { garbeam@1: brush.color = selcolor; garbeam@1: brush.border = True; garbeam@1: } garbeam@1: blitz_draw_label(&brush, i->text); garbeam@1: offx += brush.rect.width; garbeam@1: } garbeam@1: garbeam@1: brush.color = normcolor; garbeam@1: brush.border = False; garbeam@1: brush.rect.x = mrect.width - seek; garbeam@1: brush.rect.width = seek; garbeam@1: blitz_draw_label(&brush, nextoff ? ">" : nil); garbeam@1: } garbeam@1: XCopyArea(blz.dpy, brush.drawable, win, brush.gc, 0, 0, mrect.width, garbeam@1: mrect.height, 0, 0); garbeam@1: XSync(blz.dpy, False); garbeam@1: } garbeam@1: garbeam@1: static void garbeam@1: handle_kpress(XKeyEvent * e) garbeam@1: { garbeam@1: KeySym ksym; garbeam@1: char buf[32]; garbeam@1: int num, prev_nitem; garbeam@1: unsigned int i, len = strlen(text); garbeam@1: garbeam@1: buf[0] = 0; garbeam@1: num = XLookupString(e, buf, sizeof(buf), &ksym, 0); garbeam@1: garbeam@1: if(IsFunctionKey(ksym) || IsKeypadKey(ksym) garbeam@1: || IsMiscFunctionKey(ksym) || IsPFKey(ksym) garbeam@1: || IsPrivateKeypadKey(ksym)) garbeam@1: return; garbeam@1: garbeam@1: /* first check if a control mask is omitted */ garbeam@1: if(e->state & ControlMask) { garbeam@1: switch (ksym) { garbeam@1: case XK_H: garbeam@1: case XK_h: garbeam@1: ksym = XK_BackSpace; garbeam@1: break; garbeam@1: case XK_I: garbeam@1: case XK_i: garbeam@1: ksym = XK_Tab; garbeam@1: break; garbeam@1: case XK_J: garbeam@1: case XK_j: garbeam@1: ksym = XK_Return; garbeam@1: break; garbeam@1: case XK_N: garbeam@1: case XK_n: garbeam@1: ksym = XK_Right; garbeam@1: break; garbeam@1: case XK_P: garbeam@1: case XK_p: garbeam@1: ksym = XK_Left; garbeam@1: break; garbeam@1: case XK_U: garbeam@1: case XK_u: garbeam@1: text[0] = 0; garbeam@1: update_items(text); garbeam@1: draw_menu(); garbeam@1: return; garbeam@1: break; garbeam@1: case XK_bracketleft: garbeam@1: ksym = XK_Escape; garbeam@1: break; garbeam@1: default: /* ignore other control sequences */ garbeam@1: return; garbeam@1: break; garbeam@1: } garbeam@1: } garbeam@1: switch (ksym) { garbeam@1: case XK_Left: garbeam@1: if(!(sel && sel->left)) garbeam@1: return; garbeam@1: sel=sel->left; garbeam@1: if(sel->right == curroff) { garbeam@1: curroff = prevoff; garbeam@1: update_offsets(); garbeam@1: } garbeam@1: break; garbeam@1: case XK_Tab: garbeam@1: if(!sel) garbeam@1: return; garbeam@1: cext_strlcpy(text, sel->text, sizeof(text)); garbeam@1: update_items(text); garbeam@1: break; garbeam@1: case XK_Right: garbeam@1: if(!(sel && sel->right)) garbeam@1: return; garbeam@1: sel=sel->right; garbeam@1: if(sel == nextoff) { garbeam@1: curroff = nextoff; garbeam@1: update_offsets(); garbeam@1: } garbeam@1: break; garbeam@1: case XK_Return: garbeam@1: if(e->state & ShiftMask) { garbeam@1: if(text) garbeam@1: fprintf(stdout, "%s", text); garbeam@1: } garbeam@1: else if(sel) garbeam@1: fprintf(stdout, "%s", sel->text); garbeam@1: else if(text) garbeam@1: fprintf(stdout, "%s", text); garbeam@1: fflush(stdout); garbeam@1: done = True; garbeam@1: break; garbeam@1: case XK_Escape: garbeam@1: ret = 1; garbeam@1: done = True; garbeam@1: break; garbeam@1: case XK_BackSpace: garbeam@1: if((i = len)) { garbeam@1: prev_nitem = nitem; garbeam@1: do { garbeam@1: text[--i] = 0; garbeam@1: update_items(text); garbeam@1: } while(i && nitem && prev_nitem == nitem); garbeam@1: update_items(text); garbeam@1: } garbeam@1: break; garbeam@1: default: garbeam@1: if((num == 1) && !iscntrl((int) buf[0])) { garbeam@1: buf[num] = 0; garbeam@1: if(len > 0) garbeam@1: cext_strlcat(text, buf, sizeof(text)); garbeam@1: else garbeam@1: cext_strlcpy(text, buf, sizeof(text)); garbeam@1: update_items(text); garbeam@1: } garbeam@1: } garbeam@1: draw_menu(); garbeam@1: } garbeam@1: garbeam@1: static char * garbeam@1: read_allitems() garbeam@1: { garbeam@1: static char *maxname = nil; garbeam@1: char *p, buf[1024]; garbeam@1: unsigned int len = 0, max = 0; garbeam@1: Item *i, *new; garbeam@1: garbeam@1: i = nil; garbeam@1: while(fgets(buf, sizeof(buf), stdin)) { garbeam@1: len = strlen(buf); garbeam@1: if (buf[len - 1] == '\n') garbeam@1: buf[len - 1] = 0; garbeam@1: p = cext_estrdup(buf); garbeam@1: if(max < len) { garbeam@1: maxname = p; garbeam@1: max = len; garbeam@1: } garbeam@1: garbeam@1: new = cext_emalloc(sizeof(Item)); garbeam@1: new->next = new->left = new->right = nil; garbeam@1: new->text = p; garbeam@1: if(!i) garbeam@1: allitem = new; garbeam@1: else garbeam@1: i->next = new; garbeam@1: i = new; garbeam@1: } garbeam@1: garbeam@1: return maxname; garbeam@1: } garbeam@1: garbeam@1: int garbeam@1: main(int argc, char *argv[]) garbeam@1: { garbeam@1: int i; garbeam@1: XSetWindowAttributes wa; garbeam@1: char *maxname, *p; garbeam@1: BlitzFont font = {0}; garbeam@1: GC gc; garbeam@1: Drawable pmap; garbeam@1: XEvent ev; garbeam@1: garbeam@1: /* command line args */ garbeam@1: for(i = 1; i < argc; i++) { garbeam@1: if (argv[i][0] == '-') garbeam@1: switch (argv[i][1]) { garbeam@1: case 'v': garbeam@1: fprintf(stdout, "%s", version); garbeam@1: exit(0); garbeam@1: break; garbeam@1: case 't': garbeam@1: if(++i < argc) garbeam@1: title = argv[i]; garbeam@1: else garbeam@1: usage(); garbeam@1: break; garbeam@1: default: garbeam@1: usage(); garbeam@1: break; garbeam@1: } garbeam@1: else garbeam@1: usage(); garbeam@1: } garbeam@1: garbeam@1: blz.dpy = XOpenDisplay(0); garbeam@1: if(!blz.dpy) { garbeam@1: fprintf(stderr, "%s", "wmiimenu: cannot open dpy\n"); garbeam@1: exit(1); garbeam@1: } garbeam@1: blz.screen = DefaultScreen(blz.dpy); garbeam@1: blz.root = RootWindow(blz.dpy, blz.screen); garbeam@1: garbeam@1: maxname = read_allitems(); garbeam@1: garbeam@1: /* grab as early as possible, but after reading all items!!! */ garbeam@1: while(XGrabKeyboard garbeam@1: (blz.dpy, blz.root, True, GrabModeAsync, garbeam@1: GrabModeAsync, CurrentTime) != GrabSuccess) garbeam@1: usleep(1000); garbeam@1: garbeam@1: font.fontstr = getenv("WMII_FONT"); garbeam@1: if (!font.fontstr) garbeam@1: font.fontstr = cext_estrdup(BLITZ_FONT); garbeam@1: blitz_loadfont(&blz, &font); garbeam@1: garbeam@1: if((p = getenv("WMII_NORMCOLORS"))) garbeam@1: cext_strlcpy(normcolor.colstr, p, sizeof(normcolor.colstr)); garbeam@1: if(strlen(normcolor.colstr) != 23) garbeam@1: cext_strlcpy(normcolor.colstr, BLITZ_NORMCOLORS, sizeof(normcolor.colstr)); garbeam@1: blitz_loadcolor(&blz, &normcolor); garbeam@1: garbeam@1: if((p = getenv("WMII_SELCOLORS"))) garbeam@1: cext_strlcpy(selcolor.colstr, p, sizeof(selcolor.colstr)); garbeam@1: if(strlen(selcolor.colstr) != 23) garbeam@1: cext_strlcpy(selcolor.colstr, BLITZ_SELCOLORS, sizeof(selcolor.colstr)); garbeam@1: blitz_loadcolor(&blz, &selcolor); garbeam@1: garbeam@1: wa.override_redirect = 1; garbeam@1: wa.background_pixmap = ParentRelative; garbeam@1: wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask garbeam@1: | SubstructureRedirectMask | SubstructureNotifyMask; garbeam@1: garbeam@1: mrect.width = DisplayWidth(blz.dpy, blz.screen); garbeam@1: mrect.height = font.ascent + font.descent + 4; garbeam@1: mrect.y = DisplayHeight(blz.dpy, blz.screen) - mrect.height; garbeam@1: mrect.x = 0; garbeam@1: garbeam@1: win = XCreateWindow(blz.dpy, blz.root, mrect.x, mrect.y, garbeam@1: mrect.width, mrect.height, 0, DefaultDepth(blz.dpy, blz.screen), garbeam@1: CopyFromParent, DefaultVisual(blz.dpy, blz.screen), garbeam@1: CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); garbeam@1: XDefineCursor(blz.dpy, win, XCreateFontCursor(blz.dpy, XC_xterm)); garbeam@1: XSync(blz.dpy, False); garbeam@1: garbeam@1: /* pixmap */ garbeam@1: gc = XCreateGC(blz.dpy, win, 0, 0); garbeam@1: pmap = XCreatePixmap(blz.dpy, win, mrect.width, mrect.height, garbeam@1: DefaultDepth(blz.dpy, blz.screen)); garbeam@1: garbeam@1: XSync(blz.dpy, False); garbeam@1: garbeam@1: brush.blitz = &blz; garbeam@1: brush.color = normcolor; garbeam@1: brush.drawable = pmap; garbeam@1: brush.gc = gc; garbeam@1: brush.font = &font; garbeam@1: garbeam@1: if(maxname) garbeam@1: cwidth = blitz_textwidth(brush.font, maxname) + mrect.height; garbeam@1: if(cwidth > mrect.width / 3) garbeam@1: cwidth = mrect.width / 3; garbeam@1: garbeam@1: if(title) { garbeam@1: twidth = blitz_textwidth(brush.font, title) + mrect.height; garbeam@1: if(twidth > mrect.width / 3) garbeam@1: twidth = mrect.width / 3; garbeam@1: } garbeam@1: garbeam@1: cmdw = title ? twidth : cwidth; garbeam@1: garbeam@1: text[0] = 0; garbeam@1: update_items(text); garbeam@1: XMapRaised(blz.dpy, win); garbeam@1: draw_menu(); garbeam@1: XSync(blz.dpy, False); garbeam@1: garbeam@1: /* main event loop */ garbeam@1: while(!XNextEvent(blz.dpy, &ev)) { garbeam@1: switch (ev.type) { garbeam@1: case KeyPress: garbeam@1: handle_kpress(&ev.xkey); garbeam@1: break; garbeam@1: case Expose: garbeam@1: if(ev.xexpose.count == 0) { garbeam@1: draw_menu(); garbeam@1: } garbeam@1: break; garbeam@1: default: garbeam@1: break; garbeam@1: } garbeam@1: if(done) garbeam@1: break; garbeam@1: } garbeam@1: garbeam@1: XUngrabKeyboard(blz.dpy, CurrentTime); garbeam@1: XFreePixmap(blz.dpy, pmap); garbeam@1: XFreeGC(blz.dpy, gc); garbeam@1: XDestroyWindow(blz.dpy, win); garbeam@1: XCloseDisplay(blz.dpy); garbeam@1: garbeam@1: return ret; garbeam@1: }