Add wimenu

This commit is contained in:
Kris Maglione 2008-10-13 21:38:03 -04:00
parent 7cbe734db9
commit 2560525d20
6 changed files with 1098 additions and 0 deletions

26
cmd/menu/Makefile Normal file
View File

@ -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

63
cmd/menu/dat.h Normal file
View File

@ -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__)

334
cmd/menu/event.c Normal file
View File

@ -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);
}

29
cmd/menu/fns.h Normal file
View File

@ -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*);

292
cmd/menu/main.c Normal file
View File

@ -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;
}

354
cmd/menu/menu.c Normal file
View File

@ -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,
};