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>
15 #include <X11/Xft/Xft.h>
17 #define PROGNAME "xitems"
18 #define MAXKS 32
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] [-bc 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 /* warn -- print formatted string to stderr. */
47 static void
48 warn(const char *fmt, ...)
49 {
50 fputs(PROGNAME ": ", stderr);
51 if (fmt) {
52 va_list argp;
53 va_start(argp, fmt);
54 vfprintf(stderr, fmt, argp);
55 va_end(argp);
56 }
57 fputc('\n', stderr);
58 }
60 enum direction {
61 DIR_UP,
62 DIR_DOWN
63 };
65 /* doubly-linked cyclic list */
66 struct item {
67 struct item *prev;
68 struct item *next;
69 char *s;
70 KeySym ks[MAXKS]; /* array of associated keysyms */
71 size_t len; /* length of string */
72 size_t nks; /* number of associated keysyms */
73 bool dirty; /* should be redrawn */
74 };
76 static struct item *first = NULL; /* first member of the list */
77 static struct item *selected = NULL;
79 /* command-line options and X resources */
80 static char *o_font = NULL;
81 static char *o_bg = NULL, *o_fg = NULL, *o_sbg = NULL, *o_sfg = NULL,
82 *o_bc = NULL;
83 static int o_x = -1, o_y = -1;
84 static int o_bw = -1;
85 static int o_hp = -1, o_vp = -1;
87 /* X globals */
88 enum {
89 PIXEL_BG = 0,
90 PIXEL_BC,
91 };
92 #define PIXEL_N 2
94 static Display *dpy = NULL;
95 static int screen;
96 static Window win;
97 static int height, width; /* height and width of one item */
98 static XftFont *font;
99 static XftColor c_fg, c_sfg, c_sbg;
100 static unsigned long pixels[PIXEL_N];
102 /* selpos -- mark the item at position y (from top) as selected. */
103 static void
104 selpos(int y)
106 struct item *unselected = selected;
107 int top = 1;
109 selected = first;
111 if (y <= 0)
112 goto end;
114 for (selected = first; selected != first->prev;
115 selected = selected->next) {
116 if (y >= top && y < top+height)
117 break;
118 top += height;
121 end:
122 if (unselected != selected)
123 unselected->dirty = selected->dirty = true;
126 /* redraw -- redraw the entire window. */
127 static void
128 redraw(void)
130 struct item *it = first;
131 int y = 0;
133 static XftDraw *d = NULL;
135 if (!d && !(d = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen),
136 DefaultColormap(dpy, screen))))
137 die(1, "couldn't create XftDraw");
139 do {
140 if (it->dirty) {
141 XftColor *c;
143 XClearArea(dpy, win, 0, y, width, height, False);
145 if (it == selected) {
146 c = &c_sfg;
147 XftDrawRect(d, &c_sbg, 0, y, width, height);
148 } else
149 c = &c_fg;
151 XftDrawStringUtf8(d, c, font,
152 o_hp, y + o_vp+font->ascent,
153 (FcChar8 *)it->s, it->len);
155 it->dirty = false;
158 y += height;
159 it = it->next;
160 } while (it != first);
163 /*
164 * freeitems -- recursively free the list of items. First call should be
165 * freeitems(NULL).
166 */
167 static void
168 freeitems(struct item *it)
170 if (!it)
171 it = first;
172 else if (it == first)
173 return;
175 freeitems(it->next);
176 free(it);
179 /* cleanup -- free allocated resources. */
180 static void
181 cleanup(void)
183 Colormap cmap = DefaultColormap(dpy, screen);
184 Visual *vis = DefaultVisual(dpy, screen);
186 freeitems(NULL);
188 XftFontClose(dpy, font);
190 XFreeColors(dpy, cmap, pixels, PIXEL_N, 0);
191 XftColorFree(dpy, vis, cmap, &c_fg);
192 XftColorFree(dpy, vis, cmap, &c_sbg);
193 XftColorFree(dpy, vis, cmap, &c_sfg);
195 XUngrabKeyboard(dpy, CurrentTime);
196 XUngrabPointer(dpy, CurrentTime);
198 XCloseDisplay(dpy);
201 /* succeed -- exit successfully. If print is true, also print selected item. */
202 static void
203 succeed(bool print)
205 if (print && selected)
206 puts(selected->s);
207 cleanup();
208 exit(0);
211 /* scroll -- select the previous, or next item, depending on dir. */
212 static void
213 scroll(enum direction dir)
215 if (selected) {
216 selected->dirty = true;
217 selected = (dir == DIR_UP) ? selected->prev : selected->next;
218 } else
219 selected = (dir == DIR_UP) ? first->prev : first;
220 selected->dirty = true;
223 /*
224 * keyselectd -- compare ks with keysyms stored in the items list, and mark
225 * the first match as selected. Return true on match, and false otherwise.
226 */
227 static bool
228 keyselect(KeySym ks)
230 struct item *it = first;
231 KeySym k, dummy;
233 XConvertCase(ks, &k, &dummy);
235 do {
236 size_t i;
237 for (i = 0; i < it->nks; ++i)
238 if (it->ks[i] == k) {
239 selected->dirty = true;
240 selected = it;
241 selected->dirty = true;
242 return true;
244 it = it->next;
245 } while (it != first);
247 return false;
250 /* proc -- body of the main event-reading loop. */
251 static void
252 proc(void)
254 XKeyEvent ke;
255 KeySym ks;
256 char *dummy = "";
257 XEvent ev;
259 static bool inwin = false;
261 XNextEvent(dpy, &ev);
263 switch (ev.type) {
264 case EnterNotify:
265 inwin = true;
266 /* FALLTHRU */
267 case MotionNotify:
268 selpos(ev.xbutton.y);
269 /* FALLTHRU */
270 case Expose:
271 redraw();
272 break;
273 case LeaveNotify:
274 inwin = false;
275 break;
276 case ButtonPress:
277 if (ev.xbutton.button == Button4) {
278 scroll(DIR_UP);
279 redraw();
280 } else if (ev.xbutton.button == Button5) {
281 scroll(DIR_DOWN);
282 redraw();
283 } else if (inwin)
284 succeed(true);
285 else
286 succeed(false);
287 break;
288 case KeyPress:
289 ke = ev.xkey;
290 XLookupString(&ke, dummy, 0, &ks, NULL);
292 if (ke.state & ControlMask) {
293 switch (ks) {
294 case XK_bracketleft:
295 case XK_C:
296 case XK_c:
297 ks = XK_Escape;
298 break;
299 case XK_M:
300 case XK_m:
301 case XK_J:
302 case XK_j:
303 ks = XK_Return;
304 break;
305 case XK_N:
306 case XK_n:
307 ks = XK_j;
308 return;
309 case XK_P:
310 case XK_p:
311 ks = XK_k;
312 return;
314 } else if (keyselect(ks))
315 succeed(true);
317 switch (ks) {
318 case XK_j:
319 case XK_J:
320 case XK_Down:
321 scroll(DIR_DOWN);
322 redraw();
323 break;
324 case XK_k:
325 case XK_K:
326 case XK_Up:
327 scroll(DIR_UP);
328 redraw();
329 break;
330 case XK_Return:
331 succeed(true);
332 /* NOTREACHED */
333 case XK_Escape:
334 succeed(false);
335 /* NOTREACHED */
338 break;
342 /* grabptr -- try to grab pointer for a second */
343 static void
344 grabptr(void)
346 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
347 int i;
349 for (i = 0; i < 1000; ++i) {
350 if (XGrabPointer(dpy, RootWindow(dpy, screen), True,
351 ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None,
352 CurrentTime) == GrabSuccess)
353 return;
354 nanosleep(&ts, NULL);
356 die(1, "couldn't grab pointer");
359 /* grabkb -- try to grab keyboard for a second */
360 static void
361 grabkb(void)
363 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
364 int i;
366 for (i = 0; i < 1000; ++i) {
367 if (XGrabKeyboard(dpy, RootWindow(dpy, screen), True,
368 GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
369 return;
370 nanosleep(&ts, NULL);
372 die(1, "couldn't grab keyboard");
375 /* setfocus -- try setting focus to the menu for a second */
376 static void
377 setfocus(void)
379 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
380 Window focuswin;
381 int i, dummy;
383 for (i = 0; i < 1000; ++i) {
384 XGetInputFocus(dpy, &focuswin, &dummy);
385 if (focuswin == win)
386 return;
387 XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
388 nanosleep(&ts, NULL);
390 die(1, "couldn't grab keyboard");
393 /* alloccol_xft -- safely allocate new Xft colour c from string s. */
394 static void
395 alloccol_xft(char *s, XftColor *c)
397 if (!(XftColorAllocName(dpy, DefaultVisual(dpy, screen),
398 DefaultColormap(dpy, screen), s, c)))
399 die(1, "couldn't allocate Xft colour %s\n", s);
402 /* alloccol -- safely allocate new colour c from string s. */
403 static void
404 alloccol(char *s, XColor *c)
406 XColor dummy;
407 if (!(XAllocNamedColor(dpy, DefaultColormap(dpy, screen), s, c, &dummy)))
408 die(1, "couldn't allocate colour %s\n", s);
411 /*
412 * setupx -- create and map a window for n items; assign values to the X
413 * globals.
414 */
415 static void
416 setupx(int n)
418 struct item *it;
419 XColor col;
420 XClassHint ch = {PROGNAME, PROGNAME};
421 XSetWindowAttributes swa = {
422 .override_redirect = True,
423 .save_under = True,
424 .event_mask = ExposureMask | StructureNotifyMask |
425 KeyPressMask | ButtonPressMask | ButtonReleaseMask |
426 PointerMotionMask | LeaveWindowMask | EnterWindowMask,
427 };
429 if (!(font = XftFontOpenName(dpy, screen, o_font)))
430 die(1, "couldn't load font");
431 height = font->height + o_vp;
433 it = first;
434 do {
435 XGlyphInfo gi;
436 XftTextExtentsUtf8(dpy, font, (FcChar8 *)it->s, it->len, &gi);
437 if (gi.xOff + o_hp*2 > width)
438 width = gi.xOff + o_hp*2;
439 it = it->next;
440 } while (it != first);
442 if (o_x + width > DisplayWidth(dpy, screen))
443 o_x = DisplayWidth(dpy, screen) - width;
444 if (o_y + height*n > DisplayHeight(dpy, screen))
445 o_y = DisplayHeight(dpy, screen) - height*n;
447 alloccol(o_bg, &col);
448 pixels[PIXEL_BG] = swa.background_pixel = col.pixel;
449 alloccol(o_bc, &col);
450 pixels[PIXEL_BC] = swa.border_pixel = col.pixel;
452 win = XCreateWindow(dpy, RootWindow(dpy, screen), o_x, o_y,
453 width, n*height, o_bw, CopyFromParent, CopyFromParent,
454 CopyFromParent, CWOverrideRedirect | CWBackPixel | CWBorderPixel |
455 CWEventMask | CWSaveUnder, &swa);
456 XSetClassHint(dpy, win, &ch);
458 alloccol_xft(o_fg, &c_fg);
459 alloccol_xft(o_sfg, &c_sfg);
460 alloccol_xft(o_sbg, &c_sbg);
462 grabkb();
463 grabptr();
465 XMapRaised(dpy, win);
467 setfocus();
470 /*
471 * Create a new struct item, and insert it a after the it item. The rest of the
472 * arguments are values that the new item receives. On success return pointer
473 * to the new item. Return NULL otherwise.
474 */
475 static struct item *
476 insitem(struct item *it, char *s) {
477 struct item *new;
478 char *p, *end;
480 if (!(new = calloc(1, sizeof *new)))
481 return NULL;
482 if (it) {
483 new->prev = it;
484 new->next = it->next;
485 it->next->prev = new;
486 it->next = new;
487 } else
488 new->prev = new->next = new;
489 new->nks = 0;
490 new->dirty = true;
492 for (p = s; ; p = end) {
493 size_t n;
494 char c;
495 KeySym ks;
497 n = strspn(p, " ");
498 p = p + n;
499 if (!(n = strcspn(p, " \t"))) {
500 p += strspn(p, " \t");
501 break;
504 end = p + n;
505 c = *end;
506 *end = '\0';
508 if ((ks = XStringToKeysym(p)) == NoSymbol)
509 warn("no such keysym: %s\n", p);
510 else if (new->nks >= MAXKS)
511 warn("too many keysyms (%s)\n", p);
512 else {
513 KeySym k, dummy;
514 XConvertCase(ks, &k, &dummy);
515 new->ks[new->nks++] = k;
518 *end = c;
521 new->len = strlen(p);
522 new->s = strdup(p);
524 return new;
527 /*
528 * mkitems -- create a list of items from stdin, and return pointer to the
529 * new list's first element. If np is not NULL, set its value to the number of
530 * elements in the list. Return NULL on error.
531 */
532 static struct item *
533 mkitems(int *np)
535 struct item *last = NULL;
536 size_t n = 0;
537 char *line = NULL;
538 size_t linesize = 0;
539 ssize_t linelen;
541 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
542 struct item *it;
543 if (line[linelen-1] == '\n')
544 line[--linelen] = '\0';
545 if (!(it = insitem(last, line)))
546 die(1, "couldn't insert new item");
547 n++;
548 last = it;
550 free(line);
552 if (np)
553 *np = n;
555 return last ? last->next : NULL;
558 /*
559 * {s,i}default -- return the {char *,int} value of the X default, or def
560 * otherwise.
561 */
562 static char *
563 sdefault(const char *opt, const char *def)
565 char *val = XGetDefault(dpy, PROGNAME, opt);
566 return val ? val : (char *)def;
569 static int
570 idefault(const char *opt, int def)
572 char *val = XGetDefault(dpy, PROGNAME, opt);
573 return val ? atoi(val) : def;
576 /* nextarg -- safely skip the current command-line option */
577 static void
578 nextarg(int *argcp, char **argvp[])
580 if (--*argcp <= 0)
581 usage();
582 ++*argvp;
585 /*
586 * {s,i}arg -- return the {char *,int} argument of the current command-line
587 * option.
588 */
589 static char *
590 sarg(int *argcp, char **argvp[])
592 nextarg(argcp, argvp);
593 return **argvp;
596 static int
597 iarg(int *argcp, char **argvp[])
599 nextarg(argcp, argvp);
600 return atoi(**argvp);
603 /*
604 * xitems -- pop-up menu for X, constructed from stdin, and printing user choice
605 * to stdout.
606 */
607 int
608 main(int argc, char *argv[])
610 int n = 0;
612 for (--argc, ++argv; argc > 0; --argc, ++argv)
613 if (argv[0][0] == '-')
614 switch (argv[0][1]) {
615 case 'b':
616 switch (argv[0][2]) {
617 case 'c': /* -bc */
618 o_bc = sarg(&argc, &argv);
619 break;
620 case 'g': /* -bg */
621 o_bg = sarg(&argc, &argv);
622 break;
623 case 'w': /* -bw */
624 o_bw = iarg(&argc, &argv);
625 break;
626 default:
627 usage();
628 /* NOTREACHED */
630 break;
631 case 'f':
632 switch (argv[0][2]) {
633 case 'g': /* -fg */
634 o_fg = sarg(&argc, &argv);
635 break;
636 case 'o': /* -font */
637 o_font = sarg(&argc, &argv);
638 break;
639 default:
640 usage();
641 /* NOTREACHED */
643 break;
644 case 'h': /* -hp */
645 o_hp = iarg(&argc, &argv);
646 break;
647 case 's':
648 switch (argv[0][2]) {
649 case 'b': /* -sbg */
650 o_sbg = sarg(&argc, &argv);
651 break;
652 case 'f': /* -sfg */
653 o_sfg = sarg(&argc, &argv);
654 break;
655 default:
656 usage();
657 /* NOTREACHED */
659 break;
660 case 'v': /* -vp */
661 o_vp = iarg(&argc, &argv);
662 break;
663 case 'x': /* -x */
664 o_x = iarg(&argc, &argv);
665 break;
666 case 'y': /* -y */
667 o_y = iarg(&argc, &argv);
668 break;
669 default:
670 usage();
671 /* NOTREACHED */
674 if (!(dpy = XOpenDisplay(NULL)))
675 die(1, "couldn't open display");
676 screen = DefaultScreen(dpy);
678 if (!o_bg)
679 o_bg = sdefault("background", "white");
680 if (!o_fg)
681 o_fg = sdefault("foreground", "black");
682 if (!o_font)
683 o_font = sdefault("font", "DejaVu Sans Mono-10");
684 if (!o_sbg)
685 o_sbg = sdefault("selectedBackground", "black");
686 if (!o_sfg)
687 o_sfg = sdefault("selectedForeground", "white");
688 if (!o_bc)
689 o_bc = sdefault("borderColour", "black");
690 if (o_bw == -1)
691 o_bw = idefault("borderWidth", 1);
692 if (o_hp == -1)
693 o_hp = idefault("horizontalPadding", 2);
694 if (o_vp == -1)
695 o_vp = idefault("verticalPadding", 1);
697 if (o_x == -1 || o_y == -1) {
698 Window w;
699 int i;
700 unsigned int ui;
701 int *xp, *yp;
703 xp = (o_x == -1) ? &o_x : &i;
704 yp = (o_y == -1) ? &o_y : &i;
706 XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, xp, yp, &i,
707 &i, &ui);
710 if (!(first = selected = mkitems(&n))) {
711 if (n)
712 die(1, "coulnd't create items list");
713 else
714 exit(0);
716 setupx(n);
718 for (;;)
719 proc();
720 /* NOTREACHED */