/* xmenu * a simple fuzzy-selection menu made with Xlib */ #include #include #include #include #include "ui.h" #include "dynarr.h" static Display *dsp; static Window win; static int scr; static unsigned long fg, bg; static XSizeHints szhint; static GC gc; static Atom wm_delete_window; static XFontStruct *freg, *fital; void ui_init(int argc, char **argv, UiOpts opt) { dsp = XOpenDisplay(NULL); scr = DefaultScreen(dsp); fg = BlackPixel(dsp, scr); bg = WhitePixel(dsp, scr); int scr_width = XWidthOfScreen(ScreenOfDisplay(dsp, scr)); int scr_height = XHeightOfScreen(ScreenOfDisplay(dsp, scr)); szhint = (XSizeHints) { 0 }; szhint.width = 350; szhint.height = 250; szhint.flags = PSize; win = XCreateSimpleWindow( dsp, DefaultRootWindow(dsp), szhint.x, szhint.y, szhint.width, szhint.height, 2 /* border width */, fg, bg); /* TODO: replace with modern XSetWMProperties */ char title[] = "xmenu"; XSetStandardProperties(dsp, win, title, title, None, argv, argc, &szhint); gc = XCreateGC(dsp, win, 0, 0); XSetBackground(dsp, gc, bg); XSetForeground(dsp, gc, fg); XSelectInput(dsp, win, KeyPressMask | ExposureMask); wm_delete_window = XInternAtom(dsp, "WM_DELETE_WINDOW", False); XSetWMProtocols(dsp, win, &wm_delete_window, 1); /* TODO: error checking */ freg = XLoadQueryFont(dsp, "-*-new century schoolbook-medium-r-*-*-24-*-*-*-*-*-*-*"); fital = XLoadQueryFont(dsp, "-*-new century schoolbook-medium-i-*-*-24-*-*-*-*-*-*-*"); if (!freg) freg = XLoadQueryFont(dsp, "fixed"); if (!fital) fital = XLoadQueryFont(dsp, "fixed"); int w = 0; for (int i = 0; i < opt.n; i++) { int ow = XTextWidth(freg, opt.v[i].s, opt.v[i].n); if (ow > w) w = ow; } int h = 24 * (opt.n + 1); int margin = 64; if (w > scr_width - 2*margin) w = scr_width - 2*margin; if (h > scr_height - 2*margin) h = scr_height - 2*margin; XResizeWindow(dsp, win, w + 32, h + 32); XSetTransientForHint(dsp, win, DefaultRootWindow(dsp)); XMoveWindow(dsp, win, (scr_width - w - 32) / 2, (scr_height - h - 32) / 2 ); XMapRaised(dsp, win); } UiKey xksym_to_uik(KeySym sym) { switch (sym) { case XK_Escape: return UIK_ESCAPE; case XK_BackSpace: return UIK_BACKSPACE; case XK_Up: return UIK_UP; case XK_Down: return UIK_DOWN; case XK_Left: return UIK_LEFT; case XK_Right: return UIK_RIGHT; case XK_Return: return UIK_RETURN; case XK_Home: return UIK_HOME; case XK_End: return UIK_END; case XK_Page_Down: return UIK_PGDN; case XK_Page_Up: return UIK_PGUP; default: return UIK_UNKNOWN; } } /* TODO: allow selecting options with mouse */ int ui_wait_event(UiEvent *e) { XEvent ev; KeySym sym; loop: XNextEvent(dsp, &ev); switch (ev.type) { case KeyPress: e->type = UI_KEY_DOWN; e->key.strn = XLookupString(&ev.xkey, e->key.str, 10, &sym, 0); e->key.key = xksym_to_uik(sym); return 1; case KeyRelease: e->type = UI_KEY_UP; e->key.strn = XLookupString(&ev.xkey, e->key.str, 10, &sym, 0); e->key.key = xksym_to_uik(sym); return 1; case Expose: e->type = UI_REDRAW; return 1; case ClientMessage: if (ev.xclient.data.l[0] == wm_delete_window) { e->type = UI_QUIT; return 1; } break; } fprintf(stderr, "[Unknown event %d]\n", ev.type); goto loop; return 0; } void ui_draw(Str input, int inpi, int seli, UiOpts o) { XWindowAttributes attr; XClearWindow(dsp, win); XGetWindowAttributes(dsp, win, &attr); /* draw input */ XSetFont(dsp, gc, fital->fid); XDrawString(dsp, win, gc, 16, 32, input.s, input.n); int w = XTextWidth(fital, input.s, inpi); XDrawLine(dsp, win, gc, 16 + w, 32 - fital->ascent, 16 + w, 32 + fital->descent); /* draw options */ XSetFont(dsp, gc, freg->fid); int y = 64; int lines = (attr.height - 32) / 24; int tline = lines / 2; int bline = lines - tline; int scroll = seli; if (scroll > o.n - bline) scroll = o.n - bline; if (scroll < tline) scroll = tline; int top = scroll - tline; int bot = scroll + bline; if (top < 0) top = 0; if (bot > o.n) bot = o.n; for (int i = top; i < bot; i++) { if (i == seli) { int w = XTextWidth(freg, o.v[i].s, o.v[i].n); XFillRectangle(dsp, win, gc, 12, y - freg->ascent, w + 8, freg->descent + freg->ascent); XSetForeground(dsp, gc, bg); XDrawString(dsp, win, gc, 16, y, o.v[i].s, o.v[i].n); XSetForeground(dsp, gc, fg); } else { XDrawString(dsp, win, gc, 16, y, o.v[i].s, o.v[i].n); } y += 24; } } void ui_fini(void) { XFreeFont(dsp, freg); XFreeFont(dsp, fital); XFreeGC(dsp, gc); XDestroyWindow(dsp, win); XCloseDisplay(dsp); }