wmii

git clone git://oldgit.suckless.org/wmii/
Log | Files | Refs | README | LICENSE

commit 2560525d20b10a52688959fe53dfbb8373edb258
parent 7cbe734db949ad10d86237cc9d17d1013b3343d7
Author: Kris Maglione <jg@suckless.org>
Date:   Mon, 13 Oct 2008 21:38:03 -0400

Add wimenu

Diffstat:
cmd/menu/Makefile | 26++++++++++++++++++++++++++
cmd/menu/dat.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cmd/menu/event.c | 334+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cmd/menu/fns.h | 29+++++++++++++++++++++++++++++
cmd/menu/main.c | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cmd/menu/menu.c | 354+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 1098 insertions(+), 0 deletions(-)

diff --git a/cmd/menu/Makefile b/cmd/menu/Makefile @@ -0,0 +1,26 @@ +ROOT= ../.. +include $(ROOT)/mk/hdr.mk +include $(ROOT)/mk/wmii.mk + +main.c: $(ROOT)/mk/wmii.mk + +TARG = menu +HFILES= dat.h fns.h + +LIB = $(LIBIXP) +LDFLAGS += -lm $(LIBX11) -lXext -lXrandr -lXinerama \ + -lregexp9 -lbio -lfmt -lutf +CFLAGS += $(INCX11) -DVERSION=\"$(VERSION)\" \ + -DIXP_NEEDAPI=86 +OBJ = main \ + event \ + menu \ + ../wmii/geom \ + ../wmii/map \ + ../wmii/printevent \ + ../wmii/x11 \ + ../wmii/xext \ + ../util + +include $(ROOT)/mk/one.mk + diff --git a/cmd/menu/dat.h b/cmd/menu/dat.h @@ -0,0 +1,63 @@ +#define IXP_P9_STRUCTS +#define IXP_NO_P9_ +#include <fmt.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <util.h> +#include <ixp.h> +#include <x11.h> + +#define BLOCK(x) do { x; }while(0) + +#ifndef EXTERN +# define EXTERN extern +#endif + +typedef struct Item Item; + +struct Item { + char* string; + char* retstring; + Item* next_link; + Item* next; + Item* prev; + int len; + int width; +}; + +EXTERN long xtime; +EXTERN Image* ibuf; +EXTERN Font* font; +EXTERN CTuple cnorm, csel; +EXTERN bool ontop; + +EXTERN Cursor cursor[1]; +EXTERN Visual* render_visual; + +EXTERN IxpServer srv; + +EXTERN Item* items; +EXTERN Item* matchfirst; +EXTERN Item* matchstart; +EXTERN Item* matchend; +EXTERN Item* matchidx; + +EXTERN Item* histidx; + +EXTERN char filter[1024]; + +EXTERN int maxwidth; +EXTERN int result; + +EXTERN char buffer[8092]; +EXTERN char* _buffer; + +static char* const _buf_end = buffer + sizeof buffer; + +#define bufclear() \ + BLOCK( _buffer = buffer; _buffer[0] = '\0' ) +#define bufprint(...) \ + _buffer = seprint(_buffer, _buf_end, __VA_ARGS__) + diff --git a/cmd/menu/event.c b/cmd/menu/event.c @@ -0,0 +1,334 @@ +/* Copyright ©2006-2008 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +typedef void (*EvHandler)(XEvent*); +static EvHandler handler[LASTEvent]; + +void +dispatch_event(XEvent *e) { + if(e->type < nelem(handler)) { + if(handler[e->type]) + handler[e->type](e); + }else + xext_event(e); +} + +#define handle(w, fn, ev) \ + BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev)) + +static int +findtime(Display *d, XEvent *e, XPointer v) { + Window *w; + + w = (Window*)v; + if(e->type == PropertyNotify && e->xproperty.window == w->w) { + xtime = e->xproperty.time; + return true; + } + return false; +} + +void +xtime_kludge(void) { + /* Round trip. */ + static Window *w; + WinAttr wa; + XEvent e; + long l; + + if(w == nil) { + w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0); + selectinput(w, PropertyChangeMask); + } + changeprop_long(w, "ATOM", "ATOM", &l, 0); + sync(); + XIfEvent(display, &e, findtime, (void*)w); +} + +uint +flushevents(long event_mask, bool dispatch) { + XEvent ev; + uint n = 0; + + while(XCheckMaskEvent(display, event_mask, &ev)) { + if(dispatch) + dispatch_event(&ev); + n++; + } + return n; +} + +static int +findenter(Display *d, XEvent *e, XPointer v) { + long *l; + + USED(d); + l = (long*)v; + if(*l) + return false; + if(e->type == EnterNotify) + return true; + if(e->type == MotionNotify) + (*l)++; + return false; +} + +/* This isn't perfect. If there were motion events in the queue + * before this was called, then it flushes nothing. If we don't + * check for them, we might lose a legitamate enter event. + */ +uint +flushenterevents(void) { + XEvent e; + long l; + int n; + + l = 0; + n = 0; + while(XCheckIfEvent(display, &e, findenter, (void*)&l)) + n++; + return n; +} + +static void +buttonrelease(XButtonPressedEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, bup, ev); +} + +static void +buttonpress(XButtonPressedEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, bdown, ev); + else + XAllowEvents(display, ReplayPointer, ev->time); +} + +static void +configurerequest(XConfigureRequestEvent *ev) { + XWindowChanges wc; + Window *w; + + if((w = findwin(ev->window))) + handle(w, configreq, ev); + else{ + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(display, ev->window, ev->value_mask, &wc); + } +} + +static void +configurenotify(XConfigureEvent *ev) { + Window *w; + + USED(ev); + if((w = findwin(ev->window))) + handle(w, config, ev); +} + +static void +clientmessage(XClientMessageEvent *ev) { + + USED(ev); +} + +static void +destroynotify(XDestroyWindowEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, destroy, ev); +} + +static void +enternotify(XCrossingEvent *ev) { + Window *w; + static int sel_screen; + + xtime = ev->time; + if(ev->mode != NotifyNormal) + return; + + if((w = findwin(ev->window))) + handle(w, enter, ev); + else if(ev->window == scr.root.w) + sel_screen = true; +} + +static void +leavenotify(XCrossingEvent *ev) { + + xtime = ev->time; +#if 0 + if((ev->window == scr.root.w) && !ev->same_screen) + sel_screen = true; +#endif +} + +static void +focusin(XFocusChangeEvent *ev) { + Window *w; + + /* Yes, we're focusing in on nothing, here. */ + if(ev->detail == NotifyDetailNone) { + /* FIXME: Do something. */ + return; + } + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; + if((ev->mode == NotifyWhileGrabbed)) /* && (screen->hasgrab != &c_root)) */ + return; + + if((w = findwin(ev->window))) + handle(w, focusin, ev); +#if 0 + else if(ev->mode == NotifyGrab) { + if(ev->window == scr.root.w) + screen->hasgrab = &c_root; + /* Some unmanaged window has grabbed focus */ + else if((c = screen->focus)) { + print_focus("focusin", &c_magic, "<magic>"); + screen->focus = &c_magic; + if(c->sel) + frame_draw(c->sel); + } + } +#endif +} + +static void +focusout(XFocusChangeEvent *ev) { + Window *w; + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; +#if 0 + if(ev->mode == NotifyUngrab) + screen->hasgrab = nil; +#endif + + if((w = findwin(ev->window))) + handle(w, focusout, ev); +} + +static void +expose(XExposeEvent *ev) { + Window *w; + + if(ev->count == 0) { + if((w = findwin(ev->window))) + handle(w, expose, ev); + } +} + +static void +keypress(XKeyEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, kdown, ev); +} + +static void +mappingnotify(XMappingEvent *ev) { + + /* Why do you need me to tell you this? */ + XRefreshKeyboardMapping(ev); +} + +static void +maprequest(XMapRequestEvent *ev) { + + USED(ev); +} + +static void +motionnotify(XMotionEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, motion, ev); +} + +static void +propertynotify(XPropertyEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, property, ev); +} + +static void +mapnotify(XMapEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, map, ev); +} + +static void +unmapnotify(XUnmapEvent *ev) { + Window *w; + + if((w = findwin(ev->window)) && (ev->event == w->parent->w)) { + w->mapped = false; + if(ev->send_event || w->unmapped-- == 0) + handle(w, unmap, ev); + } +} + +static EvHandler handler[LASTEvent] = { + [ButtonPress] = (EvHandler)buttonpress, + [ButtonRelease] = (EvHandler)buttonrelease, + [ConfigureRequest] = (EvHandler)configurerequest, + [ConfigureNotify] = (EvHandler)configurenotify, + [ClientMessage] = (EvHandler)clientmessage, + [DestroyNotify] = (EvHandler)destroynotify, + [EnterNotify] = (EvHandler)enternotify, + [Expose] = (EvHandler)expose, + [FocusIn] = (EvHandler)focusin, + [FocusOut] = (EvHandler)focusout, + [KeyPress] = (EvHandler)keypress, + [LeaveNotify] = (EvHandler)leavenotify, + [MapNotify] = (EvHandler)mapnotify, + [MapRequest] = (EvHandler)maprequest, + [MappingNotify] = (EvHandler)mappingnotify, + [MotionNotify] = (EvHandler)motionnotify, + [PropertyNotify] = (EvHandler)propertynotify, + [UnmapNotify] = (EvHandler)unmapnotify, +}; + +void +check_x_event(IxpConn *c) { + XEvent ev; + + USED(c); + while(XCheckMaskEvent(display, ~0, &ev)) + dispatch_event(&ev); +} + diff --git a/cmd/menu/fns.h b/cmd/menu/fns.h @@ -0,0 +1,29 @@ + +void check_x_event(IxpConn*); +void debug(int, const char*, ...); +void dispatch_event(XEvent*); +Item* filter_list(Item*, char*); +uint flushenterevents(void); +uint flushevents(long, bool); +void init_screens(void); +void menu_init(void); +void menu_show(void); +void xtime_kludge(void); +void update_filter(void); + +/* geom.c */ +Align get_sticky(Rectangle src, Rectangle dst); +Cursor quad_cursor(Align); +Align quadrant(Rectangle, Point); +bool rect_contains_p(Rectangle, Rectangle); +bool rect_haspoint_p(Point, Rectangle); +bool rect_intersect_p(Rectangle, Rectangle); +Rectangle rect_intersection(Rectangle, Rectangle); + +/* xext.c */ +void randr_event(XEvent*); +bool render_argb_p(Visual*); +void xext_event(XEvent*); +void xext_init(void); +Rectangle* xinerama_screens(int*); + diff --git a/cmd/menu/main.c b/cmd/menu/main.c @@ -0,0 +1,292 @@ +/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> + * Copyright ©2006-2008 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#define IXP_NO_P9_ +#define IXP_P9_STRUCTS +#define EXTERN +#include "dat.h" +#include <X11/Xproto.h> +#include <locale.h> +#include <string.h> +#include <strings.h> +#include <bio.h> +#include "fns.h" +#define link _link + +static const char version[] = "wimenu-"VERSION", ©2008 Kris Maglione\n"; +static IxpClient* client; +static IxpCFid* ctlfid; +static Biobuf* inbuf; +static char ctl[1024]; +static char* ectl; + +static char* (*find)(const char*, const char*); + +static void +usage(void) { + fatal("usage: wimenu ...\n"); +} + +static int +errfmt(Fmt *f) { + return fmtstrcpy(f, ixp_errbuf()); +} + +/* Stubs. */ +void +debug(int flag, const char *fmt, ...) { + va_list ap; + + USED(flag); + va_start(ap, fmt); + vfprint(2, fmt, ap); + va_end(ap); +} + +void dprint(long, char*, ...); +void dprint(long mask, char *fmt, ...) { + va_list ap; + + USED(mask); + va_start(ap, fmt); + vfprint(2, fmt, ap); + va_end(ap); +} + +static char* +readctl(char *key) { + char *s, *p; + int nkey, n; + + nkey = strlen(key); + p = ctl - 1; + do { + p++; + if(!strncmp(p, key, nkey)) { + p += nkey; + s = strchr(p, '\n'); + n = (s ? s : ectl) - p; + s = emalloc(n + 1); + s[n] = '\0'; + return strncpy(s, p, n); + } + } while((p = strchr(p, '\n'))); + return ""; +} + +static void +splice(Item *i) { + i->next->prev = i->prev; + i->prev->next = i->next; +} +static void +link(Item *i, Item *j) { + i->next = j; + j->prev = i; +} + +static Item* +populate_list(Biobuf *buf, bool hist) { + Item ret; + Item *i; + char *p; + + i = &ret; + while((p = Brdstr(buf, '\n', true))) { + link(i, emallocz(sizeof *i)); + i->next_link = i->next; + i = i->next; + i->string = p; + i->retstring = p; + if(!hist) { + i->len = strlen(p); + i->width = textwidth_l(font, p, i->len); + if(i->width > maxwidth) + maxwidth = i->width; + } + } + + link(i, &ret); + splice(&ret); + return ret.next != &ret ? ret.next : nil; +} + +Item* +filter_list(Item *i, char *filter) { + static Item exact; + Item start, substr; + Item *exactp, *startp, *substrp; + Item **ip; + char *p; + int len; + + len = strlen(filter); + exactp = &exact; + startp = &start; + substrp = &substr; + for(; i; i=i->next_link) + if((p = find(i->string, filter))) { + ip = &substrp; + if(p == i->string) + if(strlen(p) == len) + ip = &exactp; + else + ip = &startp; + link(*ip, i); + *ip = i; + } + + link(substrp, &exact); + link(startp, &substr); + link(exactp, &start); + splice(&substr); + splice(&start); + splice(&exact); + return exact.next; +} + +void +update_filter(void) { + /* TODO: Perhaps filter only previous matches unless filter + * has been truncated. + */ + matchfirst = matchstart = matchidx = filter_list(items, filter); +} + +/* + * There's no way to check accesses to destroyed windows, thus + * those cases are ignored (especially on UnmapNotifies). + * Other types of errors call Xlib's default error handler, which + * calls exit(). + */ +ErrorCode ignored_xerrors[] = { + { 0, BadWindow }, + { X_SetInputFocus, BadMatch }, + { X_PolyText8, BadDrawable }, + { X_PolyFillRectangle, BadDrawable }, + { X_PolySegment, BadDrawable }, + { X_ConfigureWindow, BadMatch }, + { X_GrabKey, BadAccess }, + { X_GetAtomName, BadAtom }, +}; + +static void +end(IxpConn *c) { + + USED(c); + srv.running = 0; +} + +static void +preselect(IxpServer *s) { + + USED(s); + check_x_event(nil); +} + +void +init_screens(void) { + Rectangle *rects; + Point p; + int i, n; + + /* Pick the screen with the pointer, for now. Later, + * try for the screen with the focused window first. + */ + p = querypointer(&scr.root); + rects = xinerama_screens(&n); + for(i=0; i < n; i++) + if(rect_haspoint_p(p, rects[i])) + break; + if(i == n) + i = 0; + /* Probably not the best route. */ + scr.rect = rects[i]; + menu_show(); +} + +int +main(int argc, char *argv[]) { + Item hist = { .string = "", }; + Item *item; + char *address; + char *histfile; + int i; + + quotefmtinstall(); + fmtinstall('r', errfmt); + address = getenv("WMII_ADDRESS"); + histfile = nil; + find = strstr; + + ARGBEGIN{ + case 'a': + address = EARGF(usage()); + break; + case 'h': + histfile = EARGF(usage()); + break; + case 'i': + find = strcasestr; + break; + default: + usage(); + }ARGEND; + + if(argc) + usage(); + + setlocale(LC_CTYPE, ""); + + initdisplay(); + + if(address && *address) + client = ixp_mount(address); + else + client = ixp_nsmount("wmii"); + if(client == nil) + fatal("can't mount: %r\n"); + + ctlfid = ixp_open(client, "ctl", OREAD); + i = ixp_read(ctlfid, ctl, 1023); + ectl = ctl + i; + + srv.preselect = preselect; + ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, end); + + loadcolor(&cnorm, readctl("normcolors ")); + loadcolor(&csel, readctl("focuscolors ")); + font = loadfont(readctl("font ")); + if(!font) + fatal("Can't load font %q", readctl("font ")); + + inbuf = Bfdopen(0, OREAD); + items = populate_list(inbuf, false); + update_filter(); + + Bterm(inbuf); + histidx = &hist; + if(histfile) { + inbuf = Bopen(histfile, OREAD); + if(!inbuf) + fatal("Can't open histfile %q: %r", histfile); + item = populate_list(inbuf, true); + if(item) { + link(item->prev, &hist); + item->prev = nil; + } + Bterm(inbuf); + } + + xext_init(); + menu_init(); + init_screens(); + + i = ixp_serverloop(&srv); + if(i) + fprint(2, "%s: error: %r\n", argv0); + XCloseDisplay(display); + return result; +} + diff --git a/cmd/menu/menu.c b/cmd/menu/menu.c @@ -0,0 +1,354 @@ +#include "dat.h" +#include <ctype.h> +#include <string.h> +#include "fns.h" + +static Window* barwin; +static Handlers handlers; + +static int ltwidth; +static int numlock; + +static void menu_draw(void); + +enum { + ACCEPT, + REJECT, + HIST_NEXT, + HIST_PREV, + KILL_CHAR, + KILL_WORD, + KILL_LINE, + CMPL_NEXT, + CMPL_PREV, + CMPL_FIRST, + CMPL_LAST, + CMPL_NEXT_PAGE, + CMPL_PREV_PAGE, +}; + +void +menu_init(void) { + WinAttr wa; + + ltwidth = textwidth(font, "<"); + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | KeyPressMask; + barwin = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth, InputOutput, + &wa, CWOverrideRedirect + | CWBackPixmap + | CWEventMask); + sethandler(barwin, &handlers); +} + +static void +menu_unmap(long id, void *p) { + + USED(id, p); + unmapwin(barwin); + XFlush(display); +} + +static void +menu_cmd(int op) { + bool res; + int i; + + i = strlen(filter); + switch(op) { + case ACCEPT: + srv.running = false; + if(matchidx) + print("%s", matchidx->retstring); + else + result = 1; + break; + case REJECT: + srv.running = false; + result = 1; + break; + case HIST_NEXT: + if(histidx->next) { + histidx = histidx->next; + strncpy(filter, histidx->string, sizeof filter); + } + break; + case HIST_PREV: + if(histidx->prev) { + histidx = histidx->prev; + strncpy(filter, histidx->string, sizeof filter); + } + break; + case KILL_CHAR: + if(i > 0) + filter[i-1] = '\0'; + break; + case KILL_WORD: + if(i == 0) + break; + for(i--; i >= 0 && isspace(filter[i]); i--) + filter[i] = '\0'; + if(i >= 0) + res = !isalnum(filter[i]); + for(; i >= 0 && !isalnum(filter[i]) == res && !isspace(filter[i]); i--) + filter[i] = '\0'; + break; + case KILL_LINE: + /* TODO: Add a caret. */ + filter[0] = '\0'; + break; + case CMPL_NEXT: + matchidx = matchidx->next; + break; + case CMPL_PREV: + matchidx = matchidx->prev; + break; + case CMPL_FIRST: + matchidx = matchfirst; + break; + case CMPL_LAST: + matchidx = matchfirst->prev; + break; + case CMPL_NEXT_PAGE: + matchstart = matchend->next; + break; + case CMPL_PREV_PAGE: + matchend = matchstart->prev; + break; + } + update_filter(); + menu_draw(); +} + +static void +menu_draw(void) { + Rectangle r, r2; + CTuple *c; + Item *i; + int inputw, itemoff, end, pad; + + r = barwin->r; + r = rectsetorigin(r, ZP); + r2 = r; + + inputw = min(Dx(r) / 3, maxwidth) + pad; + itemoff = inputw + 2 * ltwidth; + end = Dx(r) - ltwidth; + pad = (font->height & ~1); + + fill(ibuf, r, cnorm.bg); + + for(i=matchstart; i->string; i=i->next) { + r2.min.x = itemoff; + itemoff = itemoff + i->width + pad; + r2.max.x = min(itemoff, end); + if(i != matchstart && itemoff > end) + break; + + c = (i == matchidx) ? &csel : &cnorm; + fill(ibuf, r2, c->bg); + drawstring(ibuf, font, r2, Center, i->string, c->fg); + matchend = i; + if(i->next == matchfirst) + break; + } + + r2 = r; + r2.min.x = inputw; + if(matchstart != matchfirst) + drawstring(ibuf, font, r2, West, "<", cnorm.fg); + if(matchend->next != matchfirst) + drawstring(ibuf, font, r2, East, ">", cnorm.fg); + r2 = r; + r2.max.x = inputw; + drawstring(ibuf, font, r2, West, filter, cnorm.fg); + + r2.min.x = textwidth(font, filter) + pad/2 + 1; + r2.max.x = r2.min.x + 2; + r2.min.y++; + r2.max.y--; + border(ibuf, r2, 1, cnorm.border); + + border(ibuf, r, 1, cnorm.border); + copyimage(barwin, r, ibuf, ZP); +} + +void +menu_show(void) { + Rectangle r; + int height, pad; + + USED(menu_unmap); + + pad = (font->height & ~1)/2; + height = font->height + 2; + + r = scr.rect; + if(ontop) + r.max.y = r.min.y + height; + else + r.min.y = r.max.y - height; + reshapewin(barwin, r); + + freeimage(ibuf); + ibuf = allocimage(Dx(r), Dy(r), scr.depth); + + mapwin(barwin); + raisewin(barwin); + menu_draw(); + setfocus(barwin, RevertToPointerRoot); +} + +static void +kdown_event(Window *w, XKeyEvent *e) { + char buf[32]; + int num, i; + KeySym ksym; + + buf[0] = 0; + num = XLookupString(e, buf, sizeof buf, &ksym, 0); + if(IsKeypadKey(ksym)) + if(ksym == XK_KP_Enter) + ksym = XK_Return; + else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) + ksym = (ksym - XK_KP_0) + XK_0; + + if(IsFunctionKey(ksym) + || IsKeypadKey(ksym) + || IsMiscFunctionKey(ksym) + || IsPFKey(ksym) + || IsPrivateKeypadKey(ksym)) + return; + + /* first check if a control mask is omitted */ + if(e->state & ControlMask) { + switch (ksym) { + default: + return; + case XK_bracketleft: /* Esc */ + menu_cmd(REJECT); + return; + case XK_j: + case XK_J: + case XK_m: + case XK_M: + menu_cmd(ACCEPT); + return; + case XK_n: + case XK_N: + menu_cmd(HIST_NEXT); + return; + case XK_p: + case XK_P: + menu_cmd(HIST_PREV); + return; + case XK_i: /* Tab */ + case XK_I: + menu_cmd(CMPL_NEXT); + return; + case XK_h: + case XK_H: + menu_cmd(KILL_CHAR); + return; + case XK_w: + case XK_W: + menu_cmd(KILL_WORD); + return; + case XK_u: + case XK_U: + menu_cmd(KILL_LINE); + return; + } + } + /* Alt-<Key> - Vim */ + if((e->state & ~(numlock | LockMask)) & Mod1Mask) { + switch(ksym) { + default: + return; + case XK_h: + menu_cmd(CMPL_PREV); + return; + case XK_l: + menu_cmd(CMPL_NEXT); + return; + case XK_j: + menu_cmd(CMPL_NEXT_PAGE); + return; + case XK_k: + menu_cmd(CMPL_PREV_PAGE); + return; + case XK_g: + menu_cmd(CMPL_FIRST); + return; + case XK_G: + menu_cmd(CMPL_LAST); + return; + } + } + switch(ksym) { + default: + if(num && !iscntrl(buf[0])) { + i = strlen(filter); + if(i < sizeof filter - 1) { + filter[i] = buf[0]; + filter[i+1] = '\0'; + } + update_filter(); + menu_draw(); + } + break; + case XK_Escape: + menu_cmd(REJECT); + return; + case XK_Return: + menu_cmd(ACCEPT); + return; + case XK_BackSpace: + menu_cmd(KILL_CHAR); + return; + case XK_Up: + menu_cmd(HIST_PREV); + return; + case XK_Down: + menu_cmd(HIST_NEXT); + return; + case XK_Home: + /* TODO: Caret. */ + menu_cmd(CMPL_FIRST); + return; + case XK_End: + /* TODO: Caret. */ + menu_cmd(CMPL_LAST); + return; + case XK_Left: + menu_cmd(CMPL_PREV); + return; + case XK_Right: + menu_cmd(CMPL_NEXT); + return; + case XK_Next: + menu_cmd(CMPL_NEXT_PAGE); + return; + case XK_Prior: + menu_cmd(CMPL_PREV_PAGE); + return; + case XK_Tab: + menu_cmd(CMPL_NEXT); + return; + } +} + +static void +expose_event(Window *w, XExposeEvent *e) { + + USED(w); + menu_draw(); +} + +static Handlers handlers = { + .expose = expose_event, + .kdown = kdown_event, +}; +