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@3: #include "config.h" garbeam@3: #include "draw.h" garbeam@3: #include "util.h" garbeam@3: 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: 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@3: static Display *dpy; garbeam@3: static Window root; garbeam@3: static Window win; garbeam@3: static XRectangle rect; garbeam@1: static Bool done = False; garbeam@3: garbeam@7: static Item *allitem = NULL; /* first of all items */ garbeam@7: static Item *item = NULL; /* first of pattern matching items */ garbeam@7: static Item *sel = NULL; garbeam@7: static Item *nextoff = NULL; garbeam@7: static Item *prevoff = NULL; garbeam@7: static Item *curroff = NULL; garbeam@3: garbeam@3: static int screen; garbeam@7: static char *title = NULL; garbeam@3: static char text[4096]; garbeam@1: static int ret = 0; 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 const int seek = 30; /* 30px */ garbeam@1: garbeam@3: static Brush brush = {0}; garbeam@3: garbeam@5: static void draw_menu(); garbeam@3: static void kpress(XKeyEvent * e); garbeam@1: garbeam@3: static char version[] = "gridmenu - " VERSION ", (C)opyright MMVI Anselm R. Garbe\n"; garbeam@1: garbeam@1: static void garbeam@1: usage() garbeam@1: { garbeam@3: fprintf(stderr, "%s", "usage: gridmenu [-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@3: tw = textwidth(&brush.font, nextoff->text); garbeam@3: if(tw > rect.width / 3) garbeam@3: tw = rect.width / 3; garbeam@3: w += tw + brush.font.height; garbeam@3: if(w > rect.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@3: tw = textwidth(&brush.font, prevoff->left->text); garbeam@3: if(tw > rect.width / 3) garbeam@3: tw = rect.width / 3; garbeam@3: w += tw + brush.font.height; garbeam@3: if(w > rect.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@7: item = j = NULL; 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@7: i->right = NULL; 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@7: i->right = NULL; 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: Item *i; garbeam@1: garbeam@3: brush.rect = rect; garbeam@1: brush.rect.x = 0; garbeam@1: brush.rect.y = 0; garbeam@3: draw(dpy, &brush, False, 0); garbeam@1: garbeam@1: /* print command */ garbeam@1: if(!title || text[0]) { garbeam@1: cmdw = cwidth; garbeam@1: if(cmdw && item) garbeam@1: brush.rect.width = cmdw; garbeam@3: draw(dpy, &brush, False, text); garbeam@1: } garbeam@1: else { garbeam@1: cmdw = twidth; garbeam@1: brush.rect.width = cmdw; garbeam@3: draw(dpy, &brush, False, title); garbeam@1: } garbeam@1: offx += brush.rect.width; garbeam@1: garbeam@1: if(curroff) { garbeam@1: brush.rect.x = offx; garbeam@1: brush.rect.width = seek; garbeam@1: offx += brush.rect.width; garbeam@3: draw(dpy, &brush, False, (curroff && curroff->left) ? "<" : 0); garbeam@1: garbeam@1: /* determine maximum items */ garbeam@1: for(i = curroff; i != nextoff; i=i->right) { garbeam@1: brush.border = False; garbeam@1: brush.rect.x = offx; garbeam@3: brush.rect.width = textwidth(&brush.font, i->text); garbeam@3: if(brush.rect.width > rect.width / 3) garbeam@3: brush.rect.width = rect.width / 3; garbeam@3: brush.rect.width += brush.font.height; garbeam@1: if(sel == i) { garbeam@3: swap((void **)&brush.fg, (void **)&brush.bg); garbeam@3: draw(dpy, &brush, True, i->text); garbeam@3: swap((void **)&brush.fg, (void **)&brush.bg); garbeam@1: } garbeam@3: else garbeam@3: draw(dpy, &brush, False, i->text); garbeam@1: offx += brush.rect.width; garbeam@1: } garbeam@1: garbeam@3: brush.rect.x = rect.width - seek; garbeam@1: brush.rect.width = seek; garbeam@3: draw(dpy, &brush, False, nextoff ? ">" : 0); garbeam@1: } garbeam@3: XCopyArea(dpy, brush.drawable, win, brush.gc, 0, 0, rect.width, garbeam@3: rect.height, 0, 0); garbeam@3: XFlush(dpy); garbeam@1: } garbeam@1: garbeam@1: static void garbeam@3: 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@3: strncpy(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@3: strncat(text, buf, sizeof(text)); garbeam@1: else garbeam@3: strncpy(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@7: static char *maxname = NULL; garbeam@1: char *p, buf[1024]; garbeam@1: unsigned int len = 0, max = 0; garbeam@1: Item *i, *new; garbeam@1: garbeam@3: i = 0; 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@3: p = estrdup(buf); garbeam@1: if(max < len) { garbeam@1: maxname = p; garbeam@1: max = len; garbeam@1: } garbeam@1: garbeam@3: new = emalloc(sizeof(Item)); garbeam@7: new->next = new->left = new->right = NULL; 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@3: char *maxname; 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@3: dpy = XOpenDisplay(0); garbeam@3: if(!dpy) garbeam@3: error("gridmenu: cannot open dpy\n"); garbeam@3: screen = DefaultScreen(dpy); garbeam@3: root = RootWindow(dpy, screen); garbeam@1: garbeam@1: maxname = read_allitems(); garbeam@1: garbeam@1: /* grab as early as possible, but after reading all items!!! */ garbeam@3: while(XGrabKeyboard(dpy, root, True, GrabModeAsync, garbeam@1: GrabModeAsync, CurrentTime) != GrabSuccess) garbeam@1: usleep(1000); garbeam@1: garbeam@3: /* style */ garbeam@3: loadcolors(dpy, screen, &brush, BGCOLOR, FGCOLOR, BORDERCOLOR); garbeam@3: loadfont(dpy, &brush.font, FONT); garbeam@1: garbeam@1: wa.override_redirect = 1; garbeam@1: wa.background_pixmap = ParentRelative; garbeam@5: wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask; garbeam@1: garbeam@3: rect.width = DisplayWidth(dpy, screen); garbeam@5: rect.height = labelheight(&brush.font); garbeam@3: rect.y = DisplayHeight(dpy, screen) - rect.height; garbeam@3: rect.x = 0; garbeam@1: garbeam@3: win = XCreateWindow(dpy, root, rect.x, rect.y, garbeam@3: rect.width, rect.height, 0, DefaultDepth(dpy, screen), garbeam@3: CopyFromParent, DefaultVisual(dpy, screen), garbeam@1: CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); garbeam@3: XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm)); garbeam@3: XFlush(dpy); garbeam@1: garbeam@1: /* pixmap */ garbeam@5: brush.gc = XCreateGC(dpy, root, 0, 0); garbeam@3: brush.drawable = XCreatePixmap(dpy, win, rect.width, rect.height, garbeam@3: DefaultDepth(dpy, screen)); garbeam@3: XFlush(dpy); garbeam@1: garbeam@1: if(maxname) garbeam@3: cwidth = textwidth(&brush.font, maxname) + brush.font.height; garbeam@3: if(cwidth > rect.width / 3) garbeam@3: cwidth = rect.width / 3; garbeam@1: garbeam@1: if(title) { garbeam@3: twidth = textwidth(&brush.font, title) + brush.font.height; garbeam@3: if(twidth > rect.width / 3) garbeam@3: twidth = rect.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@3: XMapRaised(dpy, win); garbeam@1: draw_menu(); garbeam@3: XFlush(dpy); garbeam@1: garbeam@1: /* main event loop */ garbeam@3: while(!XNextEvent(dpy, &ev)) { garbeam@1: switch (ev.type) { garbeam@1: case KeyPress: garbeam@3: 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@3: XUngrabKeyboard(dpy, CurrentTime); garbeam@3: XFreePixmap(dpy, brush.drawable); garbeam@3: XFreeGC(dpy, brush.gc); garbeam@3: XDestroyWindow(dpy, win); garbeam@3: XCloseDisplay(dpy); garbeam@1: garbeam@1: return ret; garbeam@1: }