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"
17 #define MAXKS 32
19 /* usage -- print usage information and die. */
20 static void
21 usage(void)
22 {
23 printf(
24 "usage: " PROGNAME " [-font font] [-bg colour] [-fg colour]\n"
25 " [-sbg colour] [-sfg colour] [-bc colour] [-bw width]\n"
26 " [-hp padding] [-vp padding] [-x x] [-y y]\n");
27 exit(1);
28 }
30 /* die -- print formatted string, and exit with the status eval. */
31 static void
32 die(int eval, const char *fmt, ...)
33 {
34 fputs(PROGNAME ": ", stderr);
35 if (fmt) {
36 va_list argp;
37 va_start(argp, fmt);
38 vfprintf(stderr, fmt, argp);
39 va_end(argp);
40 }
41 fputc('\n', stderr);
42 exit(eval);
43 }
45 /* warn -- print formatted string to stderr. */
46 static void
47 warn(const char *fmt, ...)
48 {
49 fputs(PROGNAME ": ", stderr);
50 if (fmt) {
51 va_list argp;
52 va_start(argp, fmt);
53 vfprintf(stderr, fmt, argp);
54 va_end(argp);
55 }
56 fputc('\n', stderr);
57 }
59 enum direction {
60 DIR_UP,
61 DIR_DOWN
62 };
64 /* doubly-linked cyclic list */
65 struct item {
66 struct item *prev;
67 struct item *next;
68 char *s;
69 KeySym ks[MAXKS]; /* array of associated keysyms */
70 size_t len; /* length of string */
71 size_t nks; /* number of associated keysyms */
72 bool dirty; /* should be redrawn */
73 };
75 static struct item *first = NULL; /* first member of the list */
76 static struct item *selected = NULL;
78 /* command-line options and X resources */
79 static char *o_font = NULL;
80 static char *o_bg = NULL, *o_fg = NULL, *o_sbg = NULL, *o_sfg = NULL,
81 *o_bc = NULL;
82 static int o_x = -1, o_y = -1;
83 static int o_bw = -1;
84 static int o_hp = -1, o_vp = -1;
86 /* X globals */
87 static Display *dpy = NULL;
88 static int screen;
89 static Window win;
90 static GC gc_sel, gc_norm;
91 static Pixmap pm_sel, pm_norm; /* background pixmaps */
92 static XFontStruct *font;
93 static int height, width; /* height and width of one item */
95 /* selpos -- mark the item at position y (from top) as selected. */
96 static void
97 selpos(int y)
98 {
99 struct item *unselected = selected;
100 int top = 1;
102 selected = first;
104 if (y <= 0)
105 goto end;
107 for (selected = first; selected != first->prev;
108 selected = selected->next) {
109 if (y >= top && y < top+height)
110 break;
111 top += height;
114 end:
115 if (unselected != selected)
116 unselected->dirty = selected->dirty = true;
119 /* redraw -- redraw the entire window */
120 static void
121 redraw(void)
123 struct item *it = first;
124 int y = 0;
126 do {
127 if (it->dirty) {
128 GC gc;
129 Pixmap pm;
131 if (it == selected) {
132 gc = gc_sel;
133 pm = pm_sel;
134 } else {
135 gc = gc_norm;
136 pm = pm_norm;
139 XCopyArea(dpy, pm, win, gc, 0, 0, width, height, 0, y);
140 XDrawString(dpy, win, gc, o_hp, y + o_vp+font->ascent,
141 it->s, it->len);
143 it->dirty = false;
146 y += height;
147 it = it->next;
148 } while (it != first);
151 /* succeed -- exit successfully. If print is true, also print selected item. */
152 static void
153 succeed(bool print)
155 if (print && selected)
156 puts(selected->s);
157 exit(0);
160 /* scroll -- select the previous, or next item, depending on dir. */
161 static void
162 scroll(enum direction dir)
164 if (selected) {
165 selected->dirty = true;
166 selected = (dir == DIR_UP) ? selected->prev : selected->next;
167 } else
168 selected = (dir == DIR_UP) ? first->prev : first;
169 selected->dirty = true;
172 /*
173 * keyselectd -- compare ks with keysyms stored in the items list, and mark
174 * the first match as selected. Return true on match, and false otherwise.
175 */
176 static bool
177 keyselect(KeySym ks)
179 struct item *it = first;
180 KeySym k, dummy;
182 XConvertCase(ks, &k, &dummy);
184 do {
185 size_t i;
186 for (i = 0; i < it->nks; ++i)
187 if (it->ks[i] == k) {
188 selected->dirty = true;
189 selected = it;
190 selected->dirty = true;
191 return true;
193 it = it->next;
194 } while (it != first);
196 return false;
199 /* proc -- body of the main event-reading loop. */
200 static void
201 proc(void)
203 XKeyEvent ke;
204 KeySym ks;
205 char *dummy = "";
206 XEvent ev;
208 static bool inwin = false;
210 XNextEvent(dpy, &ev);
212 switch (ev.type) {
213 case EnterNotify:
214 inwin = true;
215 /* FALLTHRU */
216 case MotionNotify:
217 selpos(ev.xbutton.y);
218 /* FALLTHRU */
219 case Expose:
220 redraw();
221 break;
222 case LeaveNotify:
223 inwin = false;
224 break;
225 case ButtonPress:
226 if (ev.xbutton.button == Button4) {
227 scroll(DIR_UP);
228 redraw();
229 } else if (ev.xbutton.button == Button5) {
230 scroll(DIR_DOWN);
231 redraw();
232 } else if (inwin)
233 succeed(true);
234 else
235 succeed(false);
236 break;
237 case KeyPress:
238 ke = ev.xkey;
239 XLookupString(&ke, dummy, 0, &ks, NULL);
241 if (ke.state & ControlMask) {
242 switch (ks) {
243 case XK_bracketleft:
244 case XK_C:
245 case XK_c:
246 ks = XK_Escape;
247 break;
248 case XK_M:
249 case XK_m:
250 case XK_J:
251 case XK_j:
252 ks = XK_Return;
253 break;
254 case XK_N:
255 case XK_n:
256 ks = XK_j;
257 return;
258 case XK_P:
259 case XK_p:
260 ks = XK_k;
261 return;
263 } else if (keyselect(ks))
264 succeed(true);
266 switch (ks) {
267 case XK_j:
268 case XK_J:
269 case XK_Down:
270 scroll(DIR_DOWN);
271 redraw();
272 break;
273 case XK_k:
274 case XK_K:
275 case XK_Up:
276 scroll(DIR_UP);
277 redraw();
278 break;
279 case XK_Return:
280 succeed(true);
281 /* NOTREACHED */
282 case XK_Escape:
283 succeed(false);
284 /* NOTREACHED */
287 break;
291 /* grabptr -- try to grab pointer for a second */
292 static void
293 grabptr(void)
295 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
296 int i;
298 for (i = 0; i < 1000; ++i) {
299 if (XGrabPointer(dpy, RootWindow(dpy, screen), True,
300 ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None,
301 CurrentTime) == GrabSuccess)
302 return;
303 nanosleep(&ts, NULL);
305 die(1, "couldn't grab pointer");
308 /* grabkb -- try to grab keyboard for a second */
309 static void
310 grabkb(void)
312 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
313 int i;
315 for (i = 0; i < 1000; ++i) {
316 if (XGrabKeyboard(dpy, RootWindow(dpy, screen), True,
317 GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
318 return;
319 nanosleep(&ts, NULL);
321 die(1, "couldn't grab keyboard");
324 /* setfocus -- try setting focus to the menu for a second */
325 static void
326 setfocus(void)
328 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
329 Window focuswin;
330 int i, dummy;
332 for (i = 0; i < 1000; ++i) {
333 XGetInputFocus(dpy, &focuswin, &dummy);
334 if (focuswin == win)
335 return;
336 XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
337 nanosleep(&ts, NULL);
339 die(1, "couldn't grab keyboard");
342 /*
343 * setupx -- create and map a window for n items; assign values to the X
344 * globals.
345 */
346 static void
347 setupx(int n)
349 struct item *it;
350 XGCValues gcv;
351 XColor col, dummy;
352 XClassHint ch = {PROGNAME, PROGNAME};
353 XSetWindowAttributes swa = {
354 .override_redirect = True,
355 .save_under = True,
356 .event_mask = ExposureMask | StructureNotifyMask |
357 KeyPressMask | ButtonPressMask | ButtonReleaseMask |
358 PointerMotionMask | LeaveWindowMask | EnterWindowMask,
359 };
361 if (!(font = XLoadQueryFont(dpy, o_font)))
362 die(1, "couldn't load font");
363 height = font->ascent + font->descent + o_vp;
365 it = first;
366 do {
367 int w;
368 if ((w = XTextWidth(font, it->s, it->len) + o_hp*2) > width)
369 width = w;
370 it = it->next;
371 } while (it != first);
373 if (o_x + width > DisplayWidth(dpy, screen))
374 o_x = DisplayWidth(dpy, screen) - width;
375 if (o_y + height*n > DisplayHeight(dpy, screen))
376 o_y = DisplayHeight(dpy, screen) - height*n;
378 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_bc, &col, &dummy);
379 swa.border_pixel = col.pixel;
380 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_bg, &col, &dummy);
381 swa.background_pixel = col.pixel;
383 win = XCreateWindow(dpy, RootWindow(dpy, screen), o_x, o_y,
384 width, n*height, o_bw, CopyFromParent, CopyFromParent,
385 CopyFromParent, CWOverrideRedirect | CWBackPixel | CWBorderPixel |
386 CWEventMask | CWSaveUnder, &swa);
387 XSetClassHint(dpy, win, &ch);
389 /*
390 * Foreground here means the colour with which to draw the background
391 * pixmap, i.e. the actual background colour.
392 */
393 gcv.foreground = col.pixel;
394 gc_norm = XCreateGC(dpy, win, GCForeground, &gcv);
395 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_sbg, &col,
396 &dummy);
397 gcv.foreground = col.pixel;
398 gc_sel = XCreateGC(dpy, win, GCForeground, &gcv);
400 pm_sel = XCreatePixmap(dpy, win, width, height,
401 DefaultDepth(dpy, screen));
402 pm_norm = XCreatePixmap(dpy, win, width, height,
403 DefaultDepth(dpy, screen));
404 XFillRectangle(dpy, pm_sel, gc_sel, 0, 0, width, height);
405 XFillRectangle(dpy, pm_norm, gc_norm, 0, 0, width, height);
407 /*
408 * Since the background pixmaps are already created, the GCs can be
409 * reused for text.
410 */
411 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_fg, &col,
412 &dummy);
413 XSetForeground(dpy, gc_norm, col.pixel);
414 XAllocNamedColor(dpy, DefaultColormap(dpy, screen), o_sfg, &col,
415 &dummy);
416 XSetForeground(dpy, gc_sel, col.pixel);
418 grabkb();
419 grabptr();
421 XMapRaised(dpy, win);
423 setfocus();
426 /*
427 * Create a new struct item, and insert it a after the it item. The rest of the
428 * arguments are values that the new item receives. On success return pointer
429 * to the new item. Return NULL otherwise.
430 */
431 static struct item *
432 insitem(struct item *it, char *s) {
433 struct item *new;
434 char *p, *end;
436 if (!(new = calloc(1, sizeof *new)))
437 return NULL;
438 if (it) {
439 new->prev = it;
440 new->next = it->next;
441 it->next->prev = new;
442 it->next = new;
443 } else
444 new->prev = new->next = new;
445 new->nks = 0;
446 new->dirty = true;
448 for (p = s; ; p = end) {
449 size_t n;
450 char c;
451 KeySym ks;
453 n = strspn(p, " ");
454 p = p + n;
455 if (!(n = strcspn(p, " \t"))) {
456 p += strspn(p, " \t");
457 break;
460 end = p + n;
461 c = *end;
462 *end = '\0';
464 if ((ks = XStringToKeysym(p)) == NoSymbol)
465 warn("no such keysym: %s\n", p);
466 else if (new->nks >= MAXKS)
467 warn("too many keysyms (%s)\n", p);
468 else {
469 KeySym k, dummy;
470 XConvertCase(ks, &k, &dummy);
471 new->ks[new->nks++] = k;
474 *end = c;
477 new->len = strlen(p);
478 new->s = strdup(p);
480 return new;
483 /*
484 * mkitems -- create a list of items from stdin, and return pointer to the
485 * new list's first element. If np is not NULL, set its value to the number of
486 * elements in the list. Return NULL on error.
487 */
488 static struct item *
489 mkitems(int *np)
491 struct item *last = NULL;
492 size_t n = 0;
493 char *line = NULL;
494 size_t linesize = 0;
495 ssize_t linelen;
497 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
498 struct item *it;
499 if (line[linelen-1] == '\n')
500 line[--linelen] = '\0';
501 if (!(it = insitem(last, line)))
502 die(1, "couldn't insert new item");
503 n++;
504 last = it;
506 free(line);
508 if (np)
509 *np = n;
511 return last ? last->next : NULL;
514 /*
515 * {s,i}default -- return the {char *,int} value of the X default, or def
516 * otherwise.
517 */
518 static char *
519 sdefault(const char *opt, const char *def)
521 char *val = XGetDefault(dpy, PROGNAME, opt);
522 return val ? val : (char *)def;
525 static int
526 idefault(const char *opt, int def)
528 char *val = XGetDefault(dpy, PROGNAME, opt);
529 return val ? atoi(val) : def;
532 /* nextarg -- safely skip the current command-line option */
533 static void
534 nextarg(int *argcp, char **argvp[])
536 if (--*argcp <= 0)
537 usage();
538 ++*argvp;
541 /*
542 * {s,i}arg -- return the {char *,int} argument of the current command-line
543 * option.
544 */
545 static char *
546 sarg(int *argcp, char **argvp[])
548 nextarg(argcp, argvp);
549 return **argvp;
552 static int
553 iarg(int *argcp, char **argvp[])
555 nextarg(argcp, argvp);
556 return atoi(**argvp);
559 /*
560 * xitems -- pop-up menu for X, constructed from stdin, and printing user choice
561 * to stdout.
562 */
563 int
564 main(int argc, char *argv[])
566 int n = 0;
568 for (--argc, ++argv; argc > 0; --argc, ++argv)
569 if (argv[0][0] == '-')
570 switch (argv[0][1]) {
571 case 'b':
572 switch (argv[0][2]) {
573 case 'c': /* -bc */
574 o_bc = sarg(&argc, &argv);
575 break;
576 case 'g': /* -bg */
577 o_bg = sarg(&argc, &argv);
578 break;
579 case 'w': /* -bw */
580 o_bw = iarg(&argc, &argv);
581 break;
582 default:
583 usage();
584 /* NOTREACHED */
586 break;
587 case 'f':
588 switch (argv[0][2]) {
589 case 'g': /* -fg */
590 o_fg = sarg(&argc, &argv);
591 break;
592 case 'o': /* -font */
593 o_font = sarg(&argc, &argv);
594 break;
595 default:
596 usage();
597 /* NOTREACHED */
599 break;
600 case 'h': /* -hp */
601 o_hp = iarg(&argc, &argv);
602 break;
603 case 's':
604 switch (argv[0][2]) {
605 case 'b': /* -sbg */
606 o_sbg = sarg(&argc, &argv);
607 break;
608 case 'f': /* -sfg */
609 o_sfg = sarg(&argc, &argv);
610 break;
611 default:
612 usage();
613 /* NOTREACHED */
615 break;
616 case 'v': /* -vp */
617 o_vp = iarg(&argc, &argv);
618 break;
619 case 'x': /* -x */
620 o_x = iarg(&argc, &argv);
621 break;
622 case 'y': /* -y */
623 o_y = iarg(&argc, &argv);
624 break;
625 default:
626 usage();
627 /* NOTREACHED */
630 if (!(dpy = XOpenDisplay(NULL)))
631 die(1, "couldn't open display");
632 screen = DefaultScreen(dpy);
634 if (!o_bg)
635 o_bg = sdefault("background", "white");
636 if (!o_fg)
637 o_fg = sdefault("foreground", "black");
638 if (!o_font)
639 o_font = sdefault("font", "fixed");
640 if (!o_sbg)
641 o_sbg = sdefault("selectedBackground", "black");
642 if (!o_sfg)
643 o_sfg = sdefault("selectedForeground", "white");
644 if (!o_bc)
645 o_bc = sdefault("borderColour", "black");
646 if (o_bw == -1)
647 o_bw = idefault("borderWidth", 1);
648 if (o_hp == -1)
649 o_hp = idefault("horizontalPadding", 2);
650 if (o_vp == -1)
651 o_vp = idefault("verticalPadding", 1);
653 if (o_x == -1 || o_y == -1) {
654 Window w;
655 int i;
656 unsigned int ui;
657 int *xp, *yp;
659 xp = (o_x == -1) ? &o_x : &i;
660 yp = (o_y == -1) ? &o_y : &i;
662 XQueryPointer(dpy, RootWindow(dpy, screen), &w, &w, xp, yp, &i,
663 &i, &ui);
666 if (!(first = selected = mkitems(&n))) {
667 if (n)
668 die(1, "coulnd't create items list");
669 else
670 exit(0);
672 setupx(n);
674 for (;;)
675 proc();
676 /* NOTREACHED */