Blob


1 #include <ctype.h>
2 #include <errno.h>
3 #include <stdarg.h>
4 #include <stdbool.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <time.h>
9 #include <unistd.h>
11 #include <X11/Xlib.h>
12 #include <X11/Xutil.h>
13 #include <X11/Xos.h>
14 #include <X11/Xatom.h>
16 #define PROGNAME "xitems"
18 #define LEN(A) (sizeof(A)/sizeof((A)[0]))
20 /* usage -- print usage information and die. */
21 static void
22 usage(void)
23 {
24 printf(
25 "usage: " PROGNAME " [-font font] [-bg colour] [-fg colour]\n"
26 " [-sbg colour] [-sfg colour] [-bw width]\n"
27 " [-hp padding] [-vp padding] [-x x] [-y y]\n");
28 exit(1);
29 }
31 /* die -- print formatted string, and exit with the status eval. */
32 static void
33 die(int eval, const char *fmt, ...)
34 {
35 fputs(PROGNAME ": ", stderr);
36 if (fmt) {
37 va_list argp;
38 va_start(argp, fmt);
39 vfprintf(stderr, fmt, argp);
40 va_end(argp);
41 }
42 fputc('\n', stderr);
43 exit(eval);
44 }
46 enum direction {
47 DIR_UP,
48 DIR_DOWN
49 };
51 /* doubly-linked cyclic list */
52 struct item {
53 size_t len; /* length of string */
54 char *s;
55 KeySym *ks; /* NoSym-terminated array of keysyms */
56 struct item *prev;
57 struct item *next;
58 bool dirty; /* should be redrawn */
59 };
61 static struct item *first = NULL; /* first member of the list */
62 static struct item *selected = NULL;
64 /* command-line options and X resources */
65 static char *o_font = NULL;
66 static char *o_bg = NULL, *o_fg = NULL, *o_sbg = NULL, *o_sfg = NULL;
67 static int o_x = -1, o_y = -1;
68 static int o_bw = -1;
69 static int o_hp = -1, o_vp = -1;
71 /* X globals */
72 static Display *dpy = NULL;
73 static int screen;
74 static Window win;
75 static GC gc_sel, gc_norm;
76 static Pixmap pm_sel, pm_norm; /* background pixmaps */
77 static XFontStruct *font;
78 static int height, width; /* height and width of one item */
80 /* newsel -- mark the item at position y (from top) as selected. */
81 static void
82 newsel(int y)
83 {
84 struct item *unselected = selected;
85 int top = 1;
87 selected = first;
89 if (y <= 0)
90 goto end;
92 do {
93 if (y >= top && y < top+height)
94 break;
95 top += height;
96 selected = selected->next;
97 } while (selected != first->prev);
99 end:
100 if (unselected != selected)
101 unselected->dirty = selected->dirty = true;
104 /* redraw -- redraw the entire window */
105 static void
106 redraw(void)
108 struct item *it = first;
109 int y = 0;
111 do {
112 if (it->dirty) {
113 GC gc;
114 Pixmap pm;
116 if (it == selected) {
117 gc = gc_sel;
118 pm = pm_sel;
119 } else {
120 gc = gc_norm;
121 pm = pm_norm;
124 XCopyArea(dpy, pm, win, gc, 0, 0, width, height, 0, y);
125 XDrawString(dpy, win, gc, o_hp, y + o_vp+font->ascent,
126 it->s, it->len);
128 it->dirty = false;
131 y += height;
132 it = it->next;
133 } while (it != first);
136 /* succeed -- exit successfully. If print is true, also print selected item. */
137 static void
138 succeed(bool print)
140 if (print && selected)
141 puts(selected->s);
142 exit(0);
145 /* scroll -- select the previous, or next item, depending on dir. */
146 static void
147 scroll(enum direction dir)
149 if (selected) {
150 selected->dirty = true;
151 selected = (dir == DIR_UP) ? selected->prev : selected->next;
152 } else
153 selected = (dir == DIR_UP) ? first->prev : first;
154 selected->dirty = true;
157 /* proc -- body of the main event-reading loop. */
158 static void
159 proc(void)
161 XKeyEvent ke;
162 KeySym ks;
163 char *dummy = "";
164 XEvent ev;
166 static bool inwin = false;
168 XNextEvent(dpy, &ev);
170 /* XXX try to avoid full redraws */
171 switch (ev.type) {
172 case EnterNotify:
173 inwin = true;
174 /* FALLTHRU */
175 case MotionNotify:
176 newsel(ev.xbutton.y);
177 /* FALLTHRU */
178 case Expose:
179 redraw();
180 break;
181 case LeaveNotify:
182 inwin = false;
183 break;
184 case ButtonPress:
185 if (ev.xbutton.button == Button4) {
186 scroll(DIR_UP);
187 redraw();
188 } else if (ev.xbutton.button == Button5) {
189 scroll(DIR_DOWN);
190 redraw();
191 } else if (inwin)
192 succeed(true);
193 else
194 succeed(false);
195 break;
196 case KeyPress:
197 ke = ev.xkey;
198 XLookupString(&ke, dummy, 0, &ks, NULL);
200 if (ke.state & ControlMask) {
201 switch (ks) {
202 case XK_bracketleft:
203 case XK_C:
204 case XK_c:
205 ks = XK_Escape;
206 break;
207 case XK_M:
208 case XK_m:
209 case XK_J:
210 case XK_j:
211 ks = XK_Return;
212 break;
213 case XK_N:
214 case XK_n:
215 ks = XK_j;
216 return;
217 case XK_P:
218 case XK_p:
219 ks = XK_k;
220 return;
224 switch (ks) {
225 case XK_j:
226 case XK_J:
227 case XK_Down:
228 scroll(DIR_DOWN);
229 redraw();
230 break;
231 case XK_k:
232 case XK_K:
233 case XK_Up:
234 scroll(DIR_UP);
235 redraw();
236 break;
237 case XK_Return:
238 succeed(true);
239 /* NOTREACHED */
240 case XK_Escape:
241 succeed(false);
242 /* NOTREACHED */
245 break;
249 /* grabptr -- try to grab pointer for a second */
250 static void
251 grabptr(void)
253 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
254 int i;
256 for (i = 0; i < 1000; ++i) {
257 if (XGrabPointer(dpy, RootWindow(dpy, screen), True,
258 ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None,
259 CurrentTime) == GrabSuccess)
260 return;
261 nanosleep(&ts, NULL);
263 die(1, "couldn't grab pointer");
266 /* grabkb -- try to grab keyboard for a second */
267 static void
268 grabkb(void)
270 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
271 int i;
273 for (i = 0; i < 1000; ++i) {
274 if (XGrabKeyboard(dpy, RootWindow(dpy, screen), True,
275 GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
276 return;
277 nanosleep(&ts, NULL);
279 die(1, "couldn't grab keyboard");
282 /* setfocus -- try setting focus to the menu for a second */
283 static void
284 setfocus(void)
286 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
287 Window focuswin;
288 int i, dummy;
290 for (i = 0; i < 1000; ++i) {
291 XGetInputFocus(dpy, &focuswin, &dummy);
292 if (focuswin == win)
293 return;
294 XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
295 nanosleep(&ts, NULL);
297 die(1, "couldn't grab keyboard");
300 /*
301 * setupx -- create and map a window for n items; assign values to the X
302 * globals.
303 */
304 static void
305 setupx(int n)
307 struct item *it;
308 XGCValues gcv;
309 XColor col, dummy;
310 XClassHint ch = {PROGNAME, PROGNAME};
311 XSetWindowAttributes swa = {
312 .override_redirect = True,
313 .save_under = True,
314 .event_mask = ExposureMask | StructureNotifyMask |
315 KeyPressMask | ButtonPressMask | ButtonReleaseMask |
316 PointerMotionMask | LeaveWindowMask | EnterWindowMask,
317 };
319 if (!(font = XLoadQueryFont(dpy, o_font)))
320 die(1, "couldn't load font");
321 height = font->ascent + font->descent + o_vp;
323 it = first;
324 do {
325 int w;
326 if ((w = XTextWidth(font, it->s, it->len) + o_hp*2) > width)
327 width = w;
328 it = it->next;
329 } while (it != first);
331 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_bg, &col, &dummy);
332 swa.background_pixel = col.pixel;
333 win = XCreateWindow(dpy, RootWindow(dpy, screen), o_x, o_y,
334 width, n*height, o_bw, CopyFromParent, CopyFromParent,
335 CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask |
336 CWSaveUnder, &swa);
337 XSetClassHint(dpy, win, &ch);
339 /*
340 * Foreground here means the colour with which to draw the background
341 * pixmap, i.e. the actual background colour.
342 */
343 gcv.foreground = col.pixel;
344 gc_norm = XCreateGC(dpy, win, GCForeground, &gcv);
345 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_sbg, &col,
346 &dummy);
347 gcv.foreground = col.pixel;
348 gc_sel = XCreateGC(dpy, win, GCForeground, &gcv);
350 pm_sel = XCreatePixmap(dpy, win, width, height,
351 DefaultDepth(dpy, screen));
352 pm_norm = XCreatePixmap(dpy, win, width, height,
353 DefaultDepth(dpy, screen));
354 XFillRectangle(dpy, pm_sel, gc_sel, 0, 0, width, height);
355 XFillRectangle(dpy, pm_norm, gc_norm, 0, 0, width, height);
357 /*
358 * Since the background pixmaps are already created, the GCs can be
359 * reused for text.
360 */
361 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_fg, &col,
362 &dummy);
363 XSetForeground(dpy, gc_norm, col.pixel);
364 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_sfg, &col,
365 &dummy);
366 XSetForeground(dpy, gc_sel, col.pixel);
368 grabkb();
369 grabptr();
371 XMapRaised(dpy, win);
373 setfocus();
376 /*
377 * Create a new struct item, and insert it a after the it item. The rest of the
378 * arguments are values that the new item receives. On success return pointer
379 * to the new item. Return NULL otherwise.
380 */
381 static struct item *
382 insitem(struct item *it, char *s, size_t len) {
383 struct item *new;
384 if (!(new = calloc(1, sizeof *new)))
385 return NULL;
386 if (it) {
387 new->prev = it;
388 new->next = it->next;
389 it->next->prev = new;
390 it->next = new;
391 } else
392 new->prev = new->next = new;
393 new->s = strdup(s);
394 new->len = len;
395 new->ks = NULL; /* TODO */
396 new->dirty = true;
397 return new;
400 /*
401 * mkitems -- create a list of items from stdin, and return pointer to the
402 * new list's first element. If np is not NULL, set its value to the number of
403 * elements in the list. Return NULL on error.
404 */
405 static struct item *
406 mkitems(int *np)
408 struct item *last = NULL;
409 size_t n = 0;
410 char *line = NULL;
411 size_t linesize = 0;
412 ssize_t linelen;
414 /* XXX take keysyms into account */
415 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
416 struct item *it;
417 line[--linelen] = '\0'; /* get rid of '\n' */
418 if (!(it = insitem(last, line, linelen)))
419 die(1, "couldn't insert new item");
420 n++;
421 last = it;
423 free(line);
425 if (np)
426 *np = n;
428 return last ? last->next : NULL;
431 /*
432 * {s,i}default -- return the {char *,int} value of the X default, or def
433 * otherwise.
434 */
435 static char *
436 sdefault(char *opt, char *def)
438 char *val = XGetDefault(dpy, PROGNAME, opt);
439 return val ? val : def;
442 static int
443 idefault(char *opt, int def)
445 char *val = XGetDefault(dpy, PROGNAME, opt);
446 return val ? atoi(val) : def;
449 /* nextarg -- safely skip the current command-line option */
450 static void
451 nextarg(int *argcp, char **argvp[])
453 if (--*argcp <= 0)
454 usage();
455 ++*argvp;
458 /*
459 * {s,i}arg -- return the {char *,int} argument of the current command-line
460 * option.
461 */
462 static char *
463 sarg(int *argcp, char **argvp[])
465 nextarg(argcp, argvp);
466 return **argvp;
469 static int
470 iarg(int *argcp, char **argvp[])
472 nextarg(argcp, argvp);
473 return atoi(**argvp);
476 /*
477 * xitems -- pop-up menu for X, constructed from stdin, and printing user choice
478 * to stdout.
479 */
480 int
481 main(int argc, char *argv[])
483 int n = 0;
485 for (--argc, ++argv; argc > 0; --argc, ++argv)
486 if (argv[0][0] == '-')
487 switch (argv[0][1]) {
488 case 'b':
489 switch (argv[0][2]) {
490 case 'g': /* -bg */
491 o_bg = sarg(&argc, &argv);
492 break;
493 case 'w': /* -bw */
494 o_bw = iarg(&argc, &argv);
495 break;
496 default:
497 usage();
498 /* NOTREACHED */
500 break;
501 case 'f':
502 switch (argv[0][2]) {
503 case 'g': /* -fg */
504 o_fg = sarg(&argc, &argv);
505 break;
506 case 'o': /* -font */
507 o_font = sarg(&argc, &argv);
508 break;
509 default:
510 usage();
511 /* NOTREACHED */
513 break;
514 case 'h': /* -hp */
515 o_hp = iarg(&argc, &argv);
516 break;
517 case 's':
518 switch (argv[0][2]) {
519 case 'b': /* -sbg */
520 o_sbg = sarg(&argc, &argv);
521 break;
522 case 'f': /* -sfg */
523 o_sfg = sarg(&argc, &argv);
524 break;
525 default:
526 usage();
527 /* NOTREACHED */
529 break;
530 case 'v': /* -vp */
531 o_vp = iarg(&argc, &argv);
532 break;
533 case 'x': /* -x */
534 o_x = iarg(&argc, &argv);
535 break;
536 case 'y': /* -y */
537 o_y = iarg(&argc, &argv);
538 break;
539 default:
540 usage();
541 /* NOTREACHED */
544 if (!(dpy = XOpenDisplay(NULL)))
545 die(1, "couldn't open display");
546 screen = DefaultScreen(dpy);
548 if (!o_bg)
549 o_bg = sdefault("background", "white");
550 if (!o_fg)
551 o_fg = sdefault("foreground", "black");
552 if (!o_font)
553 o_font = sdefault("font", "fixed");
554 if (!o_sbg)
555 o_sbg = sdefault("selectedBackground", "black");
556 if (!o_sfg)
557 o_sfg = sdefault("selectedForeground", "white");
558 if (o_bw == -1)
559 o_bw = idefault("borderWidth", 1);
560 if (o_hp == -1)
561 o_hp = idefault("horizontalPadding", 2);
562 if (o_vp == -1)
563 o_vp = idefault("verticalPadding", 1);
565 if (o_x == -1 || o_y == -1) {
566 Window w;
567 int i;
568 unsigned int ui;
569 int *xp, *yp;
571 xp = (o_x == -1) ? &o_x : &i;
572 yp = (o_y == -1) ? &o_y : &i;
574 XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, xp, yp, &i,
575 &i, &ui);
578 if (!(first = selected = mkitems(&n))) {
579 if (n)
580 die(1, "coulnd't create items list");
581 else
582 exit(0);
584 setupx(n);
586 for (;;)
587 proc();
588 /* NOTREACHED */