commit - 7c7bbaee4695ab7d91ef74b36ec63ac73d70881a
commit + 15aae8ae5a0d503c252bb70a4950982434434443
blob - da97bfb0362e0b84d7c36faf7b4a88202a602860
blob + dc31ae0e588c5d0756d0eaef3185e2f76f83b21a
--- xitems.c
+++ xitems.c
+#include <ctype.h>
+#include <errno.h>
#include <stdarg.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <errno.h>
-#include <ctype.h>
+#include <time.h>
#include <unistd.h>
-#include <stdbool.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
-#define HPAD 2 /* horizontal padding */
-#define VPAD 1 /* vertical padding */
-
#define PROGNAME "xitems"
#define LEN(A) (sizeof(A)/sizeof((A)[0]))
+/* usage -- print usage information and die. */
static void
usage(void)
{
- printf("too bad\n");
+ printf(
+"usage: " PROGNAME " [-font font] [-bg colour] [-fg colour]\n"
+" [-sbg colour] [-sfg colour] [-bw width]\n"
+" [-hp padding] [-vp padding] [-x x] [-y y]\n");
exit(1);
}
+/* die -- print formatted string, and exit with the status eval. */
static void
-errx(int eval, const char *fmt, ...)
+die(int eval, const char *fmt, ...)
{
fputs(PROGNAME ": ", stderr);
if (fmt) {
KeySym *ks; /* NoSym-terminated array of keysyms */
struct item *prev;
struct item *next;
+ bool dirty; /* should be redrawn */
};
-static Window win;
-static Pixmap pm_sel, pm_norm;
-static GC gc_sel, gc_norm;
-static Display *dpy;
+static struct item *first = NULL; /* first member of the list */
static struct item *selected = NULL;
+
+/* command-line options and X resources */
+static char *o_font = NULL;
+static char *o_bg = NULL, *o_fg = NULL, *o_sbg = NULL, *o_sfg = NULL;
+static int o_x = -1, o_y = -1;
+static int o_bw = -1;
+static int o_hp = -1, o_vp = -1;
+
+/* X globals */
+static Display *dpy = NULL;
static int screen;
+static Window win;
+static GC gc_sel, gc_norm;
+static Pixmap pm_sel, pm_norm; /* background pixmaps */
+static XFontStruct *font;
+static int height, width; /* height and width of one item */
-struct item *
+/* redraw -- redraw the entire window */
+static void
+redraw(void)
+{
+ struct item *it = first;
+ int y = 0;
+
+ do {
+ if (it->dirty) {
+ GC gc;
+ Pixmap pm;
+
+ if (it == selected) {
+ gc = gc_sel;
+ pm = pm_sel;
+ } else {
+ gc = gc_norm;
+ pm = pm_norm;
+ }
+
+ XCopyArea(dpy, pm, win, gc, 0, 0, width, height, 0, y);
+ XDrawString(dpy, win, gc, o_hp, y + o_vp+font->ascent,
+ it->s, it->len);
+
+ it->dirty = false;
+ }
+
+ y += height;
+ it = it->next;
+ } while (it != first);
+}
+
+/* proc -- body of the main event-reading loop. */
+static void
+proc(void)
+{
+ XKeyEvent ke;
+ KeySym ks;
+ char *dummy = "";
+ XEvent ev;
+
+ XNextEvent(dpy, &ev);
+
+ /* XXX try to avoid full redraws */
+ switch (ev.type) {
+ case Expose:
+ redraw();
+ break;
+
+ case KeyPress:
+ ke = ev.xkey;
+ XLookupString(&ke, dummy, 0, &ks, NULL);
+
+ if (ke.state & ControlMask) {
+ switch (ks) {
+ case XK_bracketleft:
+ case XK_C:
+ case XK_c:
+ ks = XK_Escape;
+ break;
+ case XK_M:
+ case XK_m:
+ case XK_J:
+ case XK_j:
+ ks = XK_Return;
+ break;
+ case XK_N:
+ case XK_n:
+ ks = XK_j;
+ return;
+ case XK_P:
+ case XK_p:
+ ks = XK_k;
+ return;
+ }
+ }
+
+ switch (ks) {
+ case XK_j:
+ case XK_J:
+ case XK_Down:
+ selected->dirty = true;
+ selected = selected ? selected->next : first;
+ selected->dirty = true;
+ redraw();
+ break;
+ case XK_k:
+ case XK_K:
+ case XK_Up:
+ selected->dirty = true;
+ selected = selected ? selected->prev : first->prev;
+ selected->dirty = true;
+ redraw();
+ break;
+ case XK_Return:
+ if (selected)
+ puts(selected->s);
+ exit(0);
+ /* NOTREACHED */
+ case XK_Escape:
+ exit(0);
+ /* NOTREACHED */
+ }
+
+ break;
+ }
+}
+
+/* grabkb -- try to grab keyboard for a second */
+static void
+grabkb(void)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
+ int i;
+
+ for (i = 0; i < 1000; ++i) {
+ if (XGrabKeyboard(dpy, RootWindow(dpy, screen), True,
+ GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
+ return;
+ nanosleep(&ts, NULL);
+ }
+ die(1, "couldn't grab keyboard");
+}
+
+/*
+ * setupx -- create and map a window for n items; assign values to the X
+ * globals.
+ */
+static void
+setupx(int n)
+{
+ struct item *it;
+ XGCValues gcv;
+ XColor col, dummy;
+ XClassHint ch = {PROGNAME, PROGNAME};
+ XSetWindowAttributes swa = {
+ .override_redirect = True,
+ .save_under = True,
+ .event_mask = ExposureMask | KeyPressMask | StructureNotifyMask,
+ };
+
+ if (!(font = XLoadQueryFont(dpy, o_font)))
+ die(1, "couldn't load font");
+ height = font->ascent + font->descent + o_vp;
+
+ it = first;
+ do {
+ int w;
+ if ((w = XTextWidth(font, it->s, it->len) + o_hp*2) > width)
+ width = w;
+ it = it->next;
+ } while (it != first);
+
+ XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_bg, &col, &dummy);
+ swa.background_pixel = col.pixel;
+ win = XCreateWindow(dpy, RootWindow(dpy, screen), o_x, o_y,
+ width, n*height, o_bw, CopyFromParent, CopyFromParent,
+ CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask |
+ CWSaveUnder, &swa);
+ XSetClassHint(dpy, win, &ch);
+
+ /*
+ * Foreground here means the colour with which to draw the background
+ * pixmap, i.e. the actual background colour.
+ */
+ gcv.foreground = col.pixel;
+ gc_norm = XCreateGC(dpy, win, GCForeground, &gcv);
+ XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_sbg, &col,
+ &dummy);
+ gcv.foreground = col.pixel;
+ gc_sel = XCreateGC(dpy, win, GCForeground, &gcv);
+
+ pm_sel = XCreatePixmap(dpy, win, width, height,
+ DefaultDepth(dpy, screen));
+ pm_norm = XCreatePixmap(dpy, win, width, height,
+ DefaultDepth(dpy, screen));
+ XFillRectangle(dpy, pm_sel, gc_sel, 0, 0, width, height);
+ XFillRectangle(dpy, pm_norm, gc_norm, 0, 0, width, height);
+
+ /*
+ * Since the background pixmaps are already created, the GCs can be
+ * reused for text.
+ */
+ XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_fg, &col,
+ &dummy);
+ XSetForeground(dpy, gc_norm, col.pixel);
+ XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_sfg, &col,
+ &dummy);
+ XSetForeground(dpy, gc_sel, col.pixel);
+
+ grabkb();
+
+ XMapRaised(dpy, win);
+}
+
+/*
+ * Create a new struct item, and insert it a after the it item. The rest of the
+ * arguments are values that the new item receives. On success return pointer
+ * to the new item. Return NULL otherwise.
+ */
+static struct item *
insitem(struct item *it, char *s, size_t len) {
struct item *new;
if (!(new = calloc(1, sizeof *new)))
new->s = strdup(s);
new->len = len;
new->ks = NULL; /* TODO */
+ new->dirty = true;
return new;
}
-void
-redraw(struct item *first, int height, int width, XFontStruct *font)
+/*
+ * mkitems -- create a list of items from stdin, and return pointer to the
+ * new list's first element. If np is not NULL, set its value to the number of
+ * elements in the list. Return NULL on error.
+ */
+static struct item *
+mkitems(int *np)
{
- struct item *it = first;
- int y_text = VPAD + font->ascent;
- int y_pm = 0;
+ struct item *last = NULL;
+ size_t n = 0;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t linelen;
- do {
- GC gc;
- Pixmap pm;
+ /* XXX take keysyms into account */
+ while ((linelen = getline(&line, &linesize, stdin)) != -1) {
+ struct item *it;
+ line[--linelen] = '\0'; /* get rid of '\n' */
+ if (!(it = insitem(last, line, linelen)))
+ die(1, "couldn't insert new item");
+ n++;
+ last = it;
+ }
+ free(line);
- if (it == selected) {
- gc = gc_sel;
- pm = pm_sel;
- } else {
- gc = gc_norm;
- pm = pm_norm;
- }
+ if (np)
+ *np = n;
- XCopyArea(dpy, pm, win, gc, 0, 0, width, height, 0, y_pm);
- XDrawString(dpy, win, gc, HPAD, y_text, it->s, it->len);
-
- y_text += height;
- y_pm += height;
- it = it->next;
- } while (it != first);
+ return last ? last->next : NULL;
}
-char *
+/*
+ * {s,i}default -- return the {char *,int} value of the X default, or def
+ * otherwise.
+ */
+static char *
sdefault(char *opt, char *def)
{
char *val = XGetDefault(dpy, PROGNAME, opt);
return val ? val : def;
}
-int
+static int
idefault(char *opt, int def)
{
char *val = XGetDefault(dpy, PROGNAME, opt);
return val ? atoi(val) : def;
}
-char *
-sarg(int *argcp, char **argvp[])
+/* nextarg -- safely skip the current command-line option */
+static void
+nextarg(int *argcp, char **argvp[])
{
if (--*argcp <= 0)
usage();
++*argvp;
+}
+
+/*
+ * {s,i}arg -- return the {char *,int} argument of the current command-line
+ * option.
+ */
+static char *
+sarg(int *argcp, char **argvp[])
+{
+ nextarg(argcp, argvp);
return **argvp;
}
-int
+static int
iarg(int *argcp, char **argvp[])
{
- if (--*argcp <= 0)
- usage();
- ++*argvp;
+ nextarg(argcp, argvp);
return atoi(**argvp);
}
+/*
+ * xitems -- pop-up menu for X, constructed from stdin, and printing user choice
+ * to stdout.
+ */
int
main(int argc, char *argv[])
{
- struct item *first = NULL, *last = NULL;
- XFontStruct *font;
- size_t linesize = 0, nitems = 0;
- ssize_t linelen;
- char *line = NULL;
- int width = 0;
- int height;
- XGCValues values;
- XSetWindowAttributes swa = {
- .override_redirect = True,
- .save_under = True,
- .event_mask = ExposureMask | KeyPressMask | StructureNotifyMask,
- };
- XClassHint ch = {PROGNAME, PROGNAME};
- XColor col1, col2;
+ int n = 0;
- char *fontname, *bg, *fg, *sbg, *sfg;
- int x, y, bw;
- fontname = bg = fg = sbg = sfg = NULL;
- x = y = bw = -1;
-
for (--argc, ++argv; argc > 0; --argc, ++argv)
if (argv[0][0] == '-')
switch (argv[0][1]) {
case 'b':
switch (argv[0][2]) {
case 'g': /* -bg */
- bg = sarg(&argc, &argv);
+ o_bg = sarg(&argc, &argv);
break;
case 'w': /* -bw */
- bw = iarg(&argc, &argv);
+ o_bw = iarg(&argc, &argv);
break;
default:
usage();
/* NOTREACHED */
}
break;
-
case 'f':
switch (argv[0][2]) {
case 'g': /* -fg */
- fg = sarg(&argc, &argv);
+ o_fg = sarg(&argc, &argv);
break;
case 'o': /* -font */
- fontname = sarg(&argc, &argv);
+ o_font = sarg(&argc, &argv);
break;
default:
usage();
/* NOTREACHED */
}
break;
-
+ case 'h': /* -hp */
+ o_hp = iarg(&argc, &argv);
+ break;
case 's':
switch (argv[0][2]) {
case 'b': /* -sbg */
- sbg = sarg(&argc, &argv);
+ o_sbg = sarg(&argc, &argv);
break;
case 'f': /* -sfg */
- sfg = sarg(&argc, &argv);
+ o_sfg = sarg(&argc, &argv);
break;
default:
usage();
/* NOTREACHED */
}
break;
-
+ case 'v': /* -vp */
+ o_vp = iarg(&argc, &argv);
+ break;
case 'x': /* -x */
- x = iarg(&argc, &argv);
+ o_x = iarg(&argc, &argv);
break;
-
case 'y': /* -y */
- y = iarg(&argc, &argv);
+ o_y = iarg(&argc, &argv);
break;
-
default:
usage();
/* NOTREACHED */
}
if (!(dpy = XOpenDisplay(NULL)))
- errx(1, "couldn't open display");
+ die(1, "couldn't open display");
screen = DefaultScreen(dpy);
- if (!bg)
- bg = sdefault("background", "white");
- if (!fg)
- fg = sdefault("foreground", "black");
- if (!fontname)
- fontname = sdefault("font", "fixed");
- if (!sbg)
- sbg = sdefault("selectedBackground", "black");
- if (!sfg)
- sfg = sdefault("selectedForeground", "white");
- if (bw == -1)
- bw = idefault("borderWidth", 1);
+ if (!o_bg)
+ o_bg = sdefault("background", "white");
+ if (!o_fg)
+ o_fg = sdefault("foreground", "black");
+ if (!o_font)
+ o_font = sdefault("font", "fixed");
+ if (!o_sbg)
+ o_sbg = sdefault("selectedBackground", "black");
+ if (!o_sfg)
+ o_sfg = sdefault("selectedForeground", "white");
+ if (o_bw == -1)
+ o_bw = idefault("borderWidth", 1);
+ if (o_hp == -1)
+ o_hp = idefault("horizontalPadding", 2);
+ if (o_vp == -1)
+ o_vp = idefault("verticalPadding", 1);
- if (x == -1 || y == -1) {
+ if (o_x == -1 || o_y == -1) {
Window w;
int i;
unsigned int ui;
int *xp, *yp;
- xp = (x == -1) ? &x : &i;
- yp = (y == -1) ? &y : &i;
+ xp = (o_x == -1) ? &o_x : &i;
+ yp = (o_y == -1) ? &o_y : &i;
XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, xp, yp, &i,
&i, &ui);
}
- font = XLoadQueryFont(dpy, fontname);
- height = font->ascent + font->descent + VPAD;
-
- /* XXX take keysyms into account */
- while ((linelen = getline(&line, &linesize, stdin)) != -1) {
- struct item *it;
- line[--linelen] = '\0'; /* get rid of '\n' */
- if (!(it = insitem(last, line, linelen)))
- exit(1);
- nitems++;
- last = it;
+ if (!(first = selected = mkitems(&n))) {
+ if (n)
+ die(1, "coulnd't create items list");
+ else
+ exit(0);
}
- free(line);
+ setupx(n);
- if (!last)
- exit(1);
- first = last->next;
-
- last = first;
- do {
- int w;
- if ((w = XTextWidth(font, last->s, last->len) + HPAD*2) > width)
- width = w;
- last = last->next;
- } while (last != first);
-
- XAllocNamedColor(dpy, DefaultColormap(dpy, screen), bg, &col1, &col2);
- swa.background_pixel = col1.pixel;
- win = XCreateWindow(dpy, RootWindow(dpy, screen), x, y,
- width, nitems*height + VPAD, bw, CopyFromParent, CopyFromParent,
- CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask,
- &swa);
- XSetClassHint(dpy, win, &ch);
-
- /*
- * Foreground here means the colour with which to draw the background
- * pixmap, i.e. the actual background colour.
- */
- values.foreground = col1.pixel;
- gc_norm = XCreateGC(dpy, win, GCForeground, &values);
- XAllocNamedColor(dpy, DefaultColormap(dpy, screen), sbg, &col1, &col2);
- values.foreground = col1.pixel;
- gc_sel = XCreateGC(dpy, win, GCForeground, &values);
-
- pm_sel = XCreatePixmap(dpy, win, width, height,
- DefaultDepth(dpy, screen));
- pm_norm = XCreatePixmap(dpy, win, width, height,
- DefaultDepth(dpy, screen));
- XFillRectangle(dpy, pm_sel, gc_sel, 0, 0, width, height);
- XFillRectangle(dpy, pm_norm, gc_norm, 0, 0, width, height);
-
- /*
- * Since the background pixmaps are already created, the GCs can be
- * reused for text.
- */
- XAllocNamedColor(dpy, DefaultColormap(dpy, screen), fg, &col1, &col2);
- XSetForeground(dpy, gc_norm, col1.pixel);
- XAllocNamedColor(dpy, DefaultColormap(dpy, screen), sfg, &col1, &col2);
- XSetForeground(dpy, gc_sel, col1.pixel);
-
- if (XGrabKeyboard(dpy, RootWindow(dpy, screen), True, GrabModeAsync,
- GrabModeAsync, CurrentTime) != GrabSuccess)
- errx(1, "cannot grab keyboard");
-
- XMapRaised(dpy, win);
-
- for (;;) {
- XKeyEvent ke;
- KeySym ks;
- char *dummy = "";
- XEvent ev;
-
- XNextEvent(dpy, &ev);
-
- /* XXX try to avoid full redraws */
- switch (ev.type) {
- case Expose:
- redraw(first, height, width, font);
- break;
- case KeyPress:
- ke = ev.xkey;
- XLookupString(&ke, dummy, 0, &ks, NULL);
-
- switch (ks) {
- case XK_q:
- case XK_Q:
- exit(0);
- /* NOTREACHED */
- case XK_j:
- case XK_J:
- selected = selected ? selected->next : first;
- redraw(first, height, width, font);
- break;
- case XK_k:
- case XK_K:
- selected = selected ? selected->prev : first->prev;
- redraw(first, height, width, font);
- break;
- case XK_Return:
- if (selected)
- puts(selected->s);
- exit(0);
- /* NOTREACHED */
- }
- break;
- }
- }
+ for (;;)
+ proc();
/* NOTREACHED */
}