1 /*
2  * ratmen.c
3  * (Note: This program is fully documented using POD.
4  * Try `pod2text ratmen.c', or use `pod2man' to generate a manpage.)
5  *
6  *
7  * This program puts up a window that is just a menu, and executes
8  * commands that correspond to the items selected.
9  *
10  * Initial idea: Arnold Robbins
11  * Version using libXg: Matty Farrow (some ideas borrowed)
12  * This code by: David Hogan and Arnold Robbins
13  *
14  * Copyright (c), Arnold Robbins and David Hogan
15  *
16  * Arnold Robbins
17  * arnold@skeeve.atl.ga.us
18  * October, 1994
19  *
20  * Code added to cause pop-up (unIconify) to move menu to mouse.
21  * Christopher Platt
22  * platt@coos.dartmouth.edu
23  * May, 1995
24  *
25  * Said code moved to -teleport option, and -warp option added.
26  * Arnold Robbins
27  * June, 1995
28  *
29  * Code added to allow --foreground and --background colors.
30  * John M. O'Donnell
31  * odonnell@stpaul.lampf.lanl.gov
32  * April, 1997
33  *
34  * Ratpoison windowmanager specific hacking; removed a lot of junk
35  * and added keyboard functionality
36  * Jonathan Walther
37  * krooger@debian.org
38  * September, 2001
39  *
40  * Derived from ratmenu and 9menu.
41  * Zrajm C Akfohg <ratmen-mail@klingonska.org>
42  *
43  */
44 
45 #include <stdio.h>
46 #include <fcntl.h>
47 #include <signal.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <sys/types.h>
52 #include <sys/wait.h>
53 #include <X11/keysym.h>
54 #include <X11/X.h>
55 #include <X11/Xlib.h>
56 #include <X11/Xutil.h>
57 #include <X11/Xatom.h>
58 #include <X11/Xresource.h>
59 #include <getopt.h>
60 
61 #define FONT "9x15bold"
62 /*
63 #define MenuMask (ExposureMask|StructureNotifyMask|KeyPressMask|FocusChangeMask)
64 */
65 #define MenuMask (ButtonPressMask|ButtonReleaseMask\
66     |LeaveWindowMask|EnterWindowMask|PointerMotionMask|ButtonMotionMask\
67     |ExposureMask|StructureNotifyMask|KeyPressMask|FocusChangeMask)
68 
69 #define MenuMaskNoMouse (ExposureMask|StructureNotifyMask\
70     |KeyPressMask|FocusChangeMask)
71 
72 
73 
74 Display *dpy;                                  /* lovely X stuff */
75 int screen;
76 Window root;
77 int display_height;                            /* height of screen in pixels */
78 Window menuwin;
79 GC gc;
80 unsigned long fg;
81 unsigned long bg;
82 
83 Colormap dcmap;
84 XColor color;
85 XFontStruct *font;
86 Atom wm_protocols;
87 Atom wm_delete_window;
88 int g_argc;         /* for XSetWMProperties to use */
89 char **g_argv;
90 int savex, savey;
91 Window savewindow;
92 
93 
94 #define Undef -1
95 char *progname;                                /* program name */
96 
97 /* menu structure */
98 char **labels;                                 /* list of menu labels */
99 char **commands;                               /* list of menu commands */
100 int numitems;                                  /* total number of items */
101 int visible_items;                             /* number of items visible */
102 
103 
104 /* program determined settings */
105 int last_item     = -1;                        /* previously selected item */
106 int last_top      = -1;                        /* previous top item in menu */
107 int full_redraw   =  True;                     /* redraw all menu items */
108 int cur_scroll_offset = Undef;
109 
110 /* command line setting variables */
111 enum  { left, center, right } align = Undef;   /* -l, -r, -c, --align WAY */
112 char *prevmenu     = NULL;                     /* -b, --back PREVMENU */
113 char *bgcname      = NULL;                     /*     --background BGCOLOR */
114 char *classname    = NULL;                     /* -C, --class CLASSNAME */
115 int   debug        = False;                    /*     --debug */
116 char *delimiter    = NULL;                     /* -d, --delimiter DELIM */
117 char *displayname;                             /* -D, --display DISPLAYNAME */
118 char *fgcname      = NULL;                     /*     --foreground FGCOLOR */
119 char *fontname     = NULL;                     /* -F, --font FNAME */
120 int   cur_item     = 0;                        /* -i, --item POSITION */
121 int   mouse_on     = Undef;                    /*     --mouse / --no-mouse */
122 enum  { print, execute } output = execute;     /* -p, --print */
123 int   scroll_offset = Undef;                   /* -o, --scroll-offset ITEMS */
124 char *shell        = "/bin/sh";                /* -S, --shell SHELL */
125 enum  { dreary, snazzy } style = Undef;        /* -s, --style STYLE */
126 char *titlename    = NULL;                     /* -t, --title NAME */
127 int   unfocus_exit = Undef;                    /* -u, --unfocus-exit */
128 
129 
130 /* function prototypes */
131 void ask_wm_for_delete(void);
132 void reap(int);
133 void redraw_snazzy(int, int, int);
134 void redraw_dreary(int, int, int);
135 void redraw_mouse (int, int, int);
136 void (*redraw) (int, int, int) = redraw_dreary;
137 
138 void run_menu(int);
139 void set_wm_hints(int, int, int);
140 void spawn(char*);
141 void help(void);
142 void version(void);
143 
144 
145 /*
146  * Event loop monitor thingy (use option `--debug') stolen from:
147  * http://www-h.eng.cam.ac.uk/help/tpl/graphics/X/X11R5/node41.html
148  */
149 static char *event_names[] = {
150     "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease",
151     "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut",
152     "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify",
153     "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest",
154     "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify",
155     "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify",
156     "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify",
157     "ClientMessage", "MappingNotify"
158 };
159 
160 
161 /* produce error message */
die(char * message)162 void die(char *message) {
163     fprintf(stderr, "%s: %s\n", progname, message);
164     fprintf(stderr, "Try `%s --help' for more information.\n", progname);
165     exit(1);
166 }
167 
168 
169 /* rewritten to use getopts by Zrajm */
args(int argc,char ** argv)170 int args(int argc, char **argv) {
171     int c;
172     while (1) {
173         static struct option long_options[] = {
174             {"align",           required_argument, 0, 'A'}, /* no shortopt */
175             {"back",            required_argument, 0, 'b'},
176             {"background",      required_argument, 0, 'B'}, /* no shortopt */
177             {"class",           required_argument, 0, 'c'},
178             {"debug",           no_argument,       0, 'X'}, /* no shortopt */
179             {"delimiter",       required_argument, 0, 'd'},
180             {"display",         required_argument, 0, 'D'},
181             {"foreground",      required_argument, 0, 'E'}, /* no shortopt */
182             {"font",            required_argument, 0, 'F'},
183             {"help",            no_argument,       0, 'h'},
184             {"item",            required_argument, 0, 'i'},
185             {"mouse",           no_argument,       0, 'm'}, /* no shortopt */
186             {"no-mouse",        no_argument,       0, 'M'}, /* no shortopt */
187             {"print",           no_argument,       0, 'p'},
188             {"scroll-offset",   required_argument, 0, 'o'},
189             {"shell",           required_argument, 0, 'S'},
190             {"style",           required_argument, 0, 's'},
191             {"title",           required_argument, 0, 't'},
192             {"unfocus-exit",    no_argument,       0, 'u'},
193             {"no-unfocus-exit", no_argument,       0, 'U'},
194             {"version",         no_argument,       0, 'V'},
195             {0, 0, 0, 0}
196         };
197 
198         /* getopt_long stores the option index here. */
199         int option_index = 0;
200         c = getopt_long(argc, argv, "b:cC:d:D:F:hi:lo:prS:s:t:V",
201             long_options, &option_index);
202 
203         /* Detect the end of the options. */
204         if (c == -1) break;
205 
206         switch (c) {
207             /*case 0:
208                 printf("gaga\n");*/
209                 /* If this option set a flag, do nothing else now. */
210                 /*if (long_options[option_index].flag != 0)
211                     break;
212                 printf ("option %s", long_options[option_index].name);
213                 if (optarg)
214                     printf (" with arg %s", optarg);
215                 printf ("\n");
216                 break;*/
217 
218             case 'A': /* --align {left|center|right} */
219                 if (strcasecmp(optarg, "left") == 0)
220                     align = left;
221                 else if (strcasecmp(optarg, "center") == 0)
222                     align = center;
223                 else if (strcasecmp(optarg, "right") == 0)
224                     align = right;
225                 else {
226                     char buffer[200] = "";
227                     sprintf(buffer, "unknown align argument `%s' "
228                         "(should be `left', `center' or `right')", optarg);
229                     die(buffer);
230                 }
231                 break;
232 
233             case 'b': /* -b, --back PREVMENU */
234                 prevmenu = optarg;
235                 break;
236 
237             case 'B': /* --background BGCOLOR */
238                 bgcname = optarg;
239                 break;
240 
241             case 'c': /* -c, --align=center */
242                 align = center;
243                 break;
244 
245             case 'C': /* -C, --class CLASSNAME */
246                 classname = optarg;
247                 break;
248 
249             case 'X': /* --debug */
250                 debug = True;
251                 break;
252 
253             case 'd': /* -d, --delimiter DELIM */
254                 delimiter = optarg;
255                 if (strcmp(delimiter, "") == 0)
256                     die("delimiter must be at least one character long");
257                 break;
258 
259             case 'D': /* -D, --display DISPLAYNAME */
260                 displayname = optarg;
261                 break;
262 
263             case 'F': /* -F, --font FNAME */
264                 fontname = optarg;
265                 break;
266 
267             case 'E': /* --foreground FGCOLOR */
268                 fgcname = optarg;
269                 break;
270 
271             case 'h': /* -h, --help */
272                 help();
273                 break;
274 
275             case 'i': /* -i, --item POSITION */
276                 cur_item = atoi(optarg) - 1;
277                 break;
278 
279             case 'm': /*     --mouse */
280                 mouse_on = True;
281                 break;
282 
283             case 'M': /*     --no-mouse */
284                 mouse_on = False;
285                 break;
286 
287             case 'l': /* -l, --align=left */
288                 align = left;
289                 break;
290 
291             case 'p': /* -p, --print */
292                 output = print;
293                 break;
294 
295             case 'o': /* -o, --scroll-offset ITEMS */
296                 scroll_offset = atoi(optarg);
297                 break;
298 
299             case 'r': /* -r, --align=right */
300                 align = right;
301                 break;
302 
303             case 'S': /* -S, --shell SHELL */
304                 shell = optarg;
305                 break;
306 
307             case 's': /* -s, --style {snazzy|dreary} */
308                 if (strcasecmp(optarg, "dreary") == 0)
309                     style = dreary;
310                 else if (strcasecmp(optarg, "snazzy") == 0)
311                     style = snazzy;
312                 else {
313                     char buffer[200] = "";
314                     sprintf(buffer, "unknown style argument `%s' "
315                         "(should be `snazzy' or `dreary')", optarg);
316                     die(buffer);
317                 }
318                 break;
319 
320             case 't': /* -t, --title NAME */
321                 titlename = optarg;
322                 break;
323 
324             case 'u': /* -u, --unfocus-exit */
325                 unfocus_exit = True;
326                 break;
327 
328             case 'U': /* -U, --no-unfocus-exit */
329                 unfocus_exit = False;
330                 break;
331 
332             case 'V': /* -V, --version */
333                 version();
334                 break;
335 
336             case '?':
337                 /* getopt_long already printed an error message. */
338                 break;
339 
340             default:
341                 abort();
342         }
343     }
344     return(optind);
345 }
346 
347 
xresource_if(int test,Display * dpy,char * progname,char * resource)348 char *xresource_if(int test, Display *dpy, char *progname, char *resource) {
349     /* char *cut = ""; */
350     char *tmp = "";
351     if (test) {
352         tmp = XGetDefault(dpy, progname, resource);
353         if (tmp != NULL) {                     /* found resource */
354             /*
355             if ((cut = strchr(tmp, ' ')))      |* trunc at 1st space *|
356                 *cut++ = '\0';
357             */
358             if (debug == True)
359                 fprintf(stderr, "  %s.%-12s: >%s<\n",
360                         progname, resource, tmp);
361             return tmp;
362         } else if (debug == True)              /* no resource found */
363             fprintf(stderr, "  %s.%-12s: [not defined in X resources]\n",
364                     progname, resource);
365     } else if (debug == True)                  /* given on command line */
366         fprintf(stderr, "  %s.%-12s: [overriden by command line]\n",
367                 progname, resource);
368     return NULL;
369 }
370 
371 
372 /* completely written by Zrajm */
xresources(Display * dpy)373 void xresources(Display *dpy) {
374     char *tmp = "";
375     if (debug == True) fprintf(stderr, "Reading X resources:\n");
376 
377     /* align: {left|center|right} */
378     tmp = xresource_if((align == Undef), dpy, classname, "align");
379     if (tmp != NULL) {
380         if      (strcasecmp(tmp, "left"  ) == 0) align = left;
381         else if (strcasecmp(tmp, "center") == 0) align = center;
382         else if (strcasecmp(tmp, "right" ) == 0) align = right;
383     }
384 
385 
386     /* background: BGCOLOR */
387     tmp = xresource_if((bgcname == NULL), dpy, classname, "background");
388     if (tmp != NULL) bgcname = tmp;
389 
390 
391     /* font: FNAME */
392     tmp = xresource_if((fontname == NULL), dpy, classname, "font");
393     if (tmp != NULL) fontname = tmp;
394 
395 
396     /* foreground: FGCOLOR */
397     tmp = xresource_if((fgcname == NULL), dpy, classname, "foreground");
398     if (tmp != NULL) fgcname = tmp;
399 
400 
401     /* mouse: {on|yes|true|off|no|false} */
402     tmp = xresource_if((mouse_on == Undef), dpy, classname, "mouse");
403     if (tmp != NULL) {
404         if      (strcasecmp(tmp, "on"  )  == 0) mouse_on = True;
405         else if (strcasecmp(tmp, "yes")   == 0) mouse_on = True;
406         else if (strcasecmp(tmp, "true")  == 0) mouse_on = True;
407         else if (strcasecmp(tmp, "off")   == 0) mouse_on = False;
408         else if (strcasecmp(tmp, "no")    == 0) mouse_on = False;
409         else if (strcasecmp(tmp, "false") == 0) mouse_on = False;
410     }
411 
412 
413     /* style: {dreary|snazzy} */
414     tmp = xresource_if((style == Undef), dpy, classname, "style");
415     if (tmp != NULL) {
416         if      (strcasecmp(tmp, "dreary") == 0) style = dreary;
417         else if (strcasecmp(tmp, "snazzy") == 0) style = snazzy;
418     }
419 
420 
421     /* scrollOffset: ITEMS */
422     tmp = xresource_if((scroll_offset == Undef), dpy, classname, "scrollOffset");
423     if (tmp != NULL) scroll_offset = atoi(tmp);
424 
425 
426     /* unfocusExit: {on|yes|true|off|no|false} */
427     tmp = xresource_if((unfocus_exit == Undef), dpy, classname, "unfocusExit");
428     if (tmp != NULL) {
429         if      (strcasecmp(tmp, "on"  )  == 0) unfocus_exit = True;
430         else if (strcasecmp(tmp, "yes")   == 0) unfocus_exit = True;
431         else if (strcasecmp(tmp, "true")  == 0) unfocus_exit = True;
432         else if (strcasecmp(tmp, "off")   == 0) unfocus_exit = False;
433         else if (strcasecmp(tmp, "no")    == 0) unfocus_exit = False;
434         else if (strcasecmp(tmp, "false") == 0) unfocus_exit = False;
435     }
436 }
437 
438 
439 /* completely written by Zrajm *|
440 void xresources(Display *dpy) {
441     XrmDatabase resourceDb = 0;
442     XrmValue    value;                     |* resource value container   *|
443     char        *cp, *tmp;
444 
445     XrmInitialize();
446     if (!(XResourceManagerString(dpy))) return;
447 
448     |* get Xresource database *|
449     resourceDb = XrmGetStringDatabase(XResourceManagerString(dpy));
450 
451     |* align: {left|center|right} *|
452     if (align == Undef &&
453         XrmGetResource(resourceDb, "ratmen*align", "", &tmp, &value)
454     ) {
455         tmp = value.addr;
456         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
457         if      (strcasecmp(tmp, "left"  ) == 0) align = left;
458         else if (strcasecmp(tmp, "center") == 0) align = center;
459         else if (strcasecmp(tmp, "right" ) == 0) align = right;
460     }
461 
462     |* background: BGCOLOR *|
463     if (bgcname == NULL &&
464         XrmGetResource(resourceDb, "ratmen*background", "", &tmp, &value)
465     ) {
466         tmp = value.addr;
467         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
468         if (tmp) bgcname = tmp;
469     }
470 
471     |* font: FNAME *|
472     if (fontname == NULL &&
473         XrmGetResource(resourceDb, "ratmen*font", "", &tmp, &value)
474     ) {
475         tmp = value.addr;
476         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
477         if (tmp) fontname = tmp;
478     }
479 
480     |* foreground: FGCOLOR *|
481     if (fgcname == NULL &&
482         XrmGetResource(resourceDb, "ratmen*foreground", "", &tmp, &value)
483     ) {
484         tmp = value.addr;
485         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
486         if (tmp) fgcname = tmp;
487     }
488 
489     |* style: {dreary|snazzy} *|
490     if (style == Undef &&
491         XrmGetResource(resourceDb, "ratmen*style", "", &tmp, &value)
492     ) {
493         tmp = value.addr;
494         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
495         if      (strcasecmp(tmp, "dreary") == 0) style = dreary;
496         else if (strcasecmp(tmp, "snazzy") == 0) style = snazzy;
497     }
498 
499     |* scrollOffset: ITEMS *|
500     if (scroll_offset == Undef &&
501         XrmGetResource(resourceDb, "ratmen*scrollOffset", "", &tmp, &value)
502     ) {
503         tmp = value.addr;
504         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
505         scroll_offset = atoi(tmp);
506     }
507 
508     |* unfocusExit: {on|yes|true|off|no|false} *|
509     if (unfocus_exit == Undef &&
510         XrmGetResource(resourceDb, "ratmen*unfocusExit", "", &tmp, &value)
511     ) {
512         tmp = value.addr;
513         if ((cp = strchr(tmp, ' '))) *cp++ = '\0';
514         if      (strcasecmp(tmp, "on"  )  == 0) unfocus_exit = True;
515         else if (strcasecmp(tmp, "yes")   == 0) unfocus_exit = True;
516         else if (strcasecmp(tmp, "true")  == 0) unfocus_exit = True;
517         else if (strcasecmp(tmp, "off")   == 0) unfocus_exit = False;
518         else if (strcasecmp(tmp, "no")    == 0) unfocus_exit = False;
519         else if (strcasecmp(tmp, "false") == 0) unfocus_exit = False;
520     }
521     XrmDestroyDatabase(resourceDb);
522 }
523 */
524 
525 
items(int start,int count,char ** arg)526 void items(int start, int count, char **arg) {
527     int j;
528     char *cut;
529 
530     if (delimiter) {                           /* using delimiter */
531         /* do things similar to `9menu' */
532         if (count-start < 1) die("not enough arguments");
533 
534         numitems = count-start;
535         labels   = (char **) malloc(numitems*sizeof(char *));
536         commands = (char **) malloc(numitems*sizeof(char *));
537         if (commands == NULL || labels == NULL)
538             die("cannot allocate memory for command and label arrays");
539 
540         for (j = 0; j < numitems; j ++) {
541             labels[j] = arg[start+j];
542             if ((cut  = strstr(labels[j], delimiter)) != NULL) {
543                 *cut  = '\0';
544                  cut += strlen(delimiter);
545                 commands[j] = cut;
546             } else
547                 commands[j] = labels[j];
548         }
549         return;
550     } else {                                   /* without delimiter */
551         /* do as `ratmenu' does */
552         if ((count-start) % 2 != 0) die("not an even number of menu arguments");
553         if ((count-start) * 2 <  1) die("not enough arguments");
554 
555         numitems = (count-start) / 2;
556         labels   = (char **) malloc(numitems*sizeof(char *));
557         commands = (char **) malloc(numitems*sizeof(char *));
558         if (commands == NULL || labels == NULL)
559             die("cannot allocate memory for command and label arrays");
560 
561         for (j=0; start < count; j ++) {
562             labels[j]   = arg[start++];
563             commands[j] = arg[start++];
564             if (strcmp(commands[j], "") == 0)
565                 commands[j] = labels[j];
566         }
567         return;
568     }
569 }
570 
571 
HandleXError(Display * dpy,XErrorEvent * event)572 int HandleXError(Display *dpy, XErrorEvent *event) {
573 /*    gXErrorFlag = 1;*/
574     return 0;
575 }
576 
577 
578 
579 
580 /* main --- crack arguments, set up X stuff, run the main menu loop */
main(int argc,char ** argv)581 int main(int argc, char **argv) {
582     int i;
583     char *cp;
584     XGCValues gv;
585     unsigned long mask;
586     g_argc = argc;                             /* save command line args */
587     g_argv = argv;
588 
589 
590     /* get program name (=default X resource class & title) */
591     if ((cp = strrchr(argv[0], '/')))          /* if argv[0] contains slash  */
592         progname = ++cp;                       /*   use all after that       */
593     else                                       /* otherwise                  */
594         progname = argv[0];                    /*   use argv[0] as is        */
595 
596     /* set defaults for non-resource options */
597     titlename = progname;                      /* default window title */
598     classname = progname;                      /* default X resource class */
599     i = args(argc, argv);                      /* process command line args */
600     items(i, argc, argv);                      /* process menu items */
601 
602     dpy = XOpenDisplay(displayname);
603     if (dpy == NULL) {
604         fprintf(stderr, "%s: cannot open display", progname);
605         if (displayname != NULL)
606             fprintf(stderr, " %s", displayname);
607         fprintf(stderr, "\n");
608         exit(1);
609     }
610 
611     /* make it ignore BadWindow errors */
612     XSetErrorHandler(HandleXError);
613     XFlush(dpy);
614 
615     /* use Xresource for undefined values */
616     xresources(dpy);
617 
618     /* defaults (for undefined cases) */
619     if (fontname == NULL) fontname = FONT;     /* default font */
620     if (scroll_offset == Undef)                /* default scroll offset */
621         scroll_offset = 3;
622     if (mouse_on == Undef)                     /* mouse menu selection */
623         mouse_on = True;
624     if (style == snazzy) {                     /* default display style */
625         redraw = redraw_snazzy;
626     } else {
627         redraw = redraw_dreary;
628     }
629     cur_scroll_offset = scroll_offset;
630 
631     screen         = DefaultScreen(dpy);
632     root           = RootWindow(dpy, screen);
633     display_height = DisplayHeight(dpy, screen);
634 
635     dcmap = DefaultColormap (dpy, screen);
636     if (fgcname == NULL ||
637         XParseColor(dpy, dcmap, fgcname, &color) == 0 ||
638         XAllocColor(dpy, dcmap, &color) == 0
639     ) {
640         fg = WhitePixel(dpy, screen);
641     } else { fg = color.pixel; }
642 
643     if (bgcname == NULL ||
644         XParseColor(dpy, dcmap, bgcname, &color) == 0 ||
645         XAllocColor(dpy, dcmap, &color) == 0
646     ) {
647         bg = BlackPixel(dpy, screen);
648     } else { bg = color.pixel; }
649 
650     if ((font = XLoadQueryFont(dpy, fontname)) == NULL) {
651         fprintf(stderr, "%s: fatal: cannot load font `%s'\n", progname, fontname);
652         exit(1);
653     }
654 
655     gv.foreground = fg^bg;
656     gv.background = bg;
657     gv.font = font->fid;
658     gv.function = GXxor;
659     gv.line_width = 0;
660     mask = GCForeground | GCBackground | GCFunction | GCFont | GCLineWidth;
661     gc = XCreateGC(dpy, root, mask, &gv);
662 
663     signal(SIGCHLD, reap);
664 
665     run_menu(cur_item);
666 
667     /* XCloseDisplay() cannot generate BadWindow */
668     XCloseDisplay(dpy);
669     exit(0);
670 }
671 
672 
673 /* spawn --- run a command */
spawn(char * com)674 void spawn(char *com) {
675     int pid;
676     static char *sh_base = NULL;
677 
678     if (sh_base == NULL) {
679         sh_base = strrchr(shell, '/');
680         if (sh_base != NULL)
681             sh_base++;
682         else
683             sh_base = shell;
684     }
685 
686     pid = fork();
687     if (pid < 0) {
688         fprintf(stderr, "%s: can't fork\n", progname);
689         return;
690     } else if (pid > 0)
691         return;
692 
693     close(ConnectionNumber(dpy));
694     execl(shell, sh_base, "-c", com, NULL);
695     _exit(1);
696 }
697 
698 
699 /* reap --- collect dead children */
reap(int s)700 void reap(int s) {
701     (void) wait((int *) NULL);
702     signal(s, reap);
703 }
704 
705 
version(void)706 void version(void) {
707     printf("%s (for Ratpoison) 2.2.3 (compiled 25 October 2007)\n"
708         "Written by Arnold Robbins & David Hogan (1994-1995),\n"
709         "John M. O'Donnell (1997), Jonathan Walther (2001) and\n"
710         "Zrajm C Akfohg (2003, 2007).\n"
711         "\n"
712         "Copyright 1994-1995, 1997, 2001, 2003, 2007 by respective author.\n"
713         "Distributed under the GNU Public License.\n", progname);
714     exit(0);
715 }
716 
717 
718 /* usage --- print a usage message and die */
help(void)719 void help(void) {
720     printf("Usage: %s [OPTION]... MENUITEM COMMAND ...\n"
721         "   or: %s [OPTION]... {-d##|--delimiter ##} MENUITEM##COMMAND ...\n"
722         "Create a simple menu in a separate window and run user selected command.\n"
723         "\n", progname, progname);
724     printf(
725         "  -l, -c, -r,                      set window text alignment (Xresource)\n"
726         "        --align {left|center|right}\n"
727         "  -b, --back PREVMENU              command to run on `back'\n"
728         "      --background BGCOLOR         set background color (Xresource)\n"
729         "  -C, --class CLASSNAME            CLASSNAME of Xresources to look for\n"
730         "      --debug                      enable debug output on stderr\n"
731         "  -d, --delimiter DELIM            pair up each MENUITEM and COMMAND\n");
732     printf(
733         "                                   in one argument separated by DELIM\n"
734         "  -D, --display DISPLAYNAME        open menu on named X display\n"
735         "      --foreground FGCOLOR         set foreground color (Xresource)\n"
736         "  -F, --font FNAME                 Use X font FNAME (Xresource)\n"
737         "  -h, --help                       display this help and exit\n"
738         "  -i, --item POSITION              set MENUITEM selected on startup\n"
739         "      --mouse / --no-mouse         enable/disable mouse support (Xresource)\n");
740     printf(
741         "  -p, --print                      print COMMAND on stdout, don't execute\n"
742         "  -o, --scroll-offset ITEMS        border distance for scrolling (Xresource)\n"
743         "  -S, --shell SHELL                SHELL in which to run commands\n"
744         "  -s, --style {snazzy|dreary}      select menu style (Xresource)\n"
745         "  -t, --title NAME                 set window title to NAME\n"
746         "      --unfocus-exit /             enable/disable exit on loosing window\n"
747         "        --no-unfocus-exit            focus (Xresource)\n");
748     printf(
749         "  -V, --version                    output version information and exit\n"
750         "\n");
751 
752     exit(0);
753 }
754 
755 
756 /* run_menu --- put up the window, execute selected commands */
run_menu(int cur)757 void run_menu(int cur) {
758     KeySym key;
759     XEvent event;
760     XClientMessageEvent *cmsg;
761     int i, wide, high, dx, dy, mousing = False, cur_store = cur;
762 
763     /* Currently selected menu item */
764     if (cur  <  -1)       cur = 0;
765     if (cur  >= numitems) cur = numitems-1;
766 
767     /* find widest menu item */
768     dx = 0;
769     for (i = 0; i < numitems; i++) {
770         wide = XTextWidth(font, labels[i], strlen(labels[i])) + 4;
771         if (wide > dx)
772             dx = wide;
773     }
774     wide = dx;
775 
776     high = font->ascent + font->descent + 1;
777     dy   = numitems * high;                    /* height of menu window */
778     if (dy > display_height)                   /* shrink if outside screen */
779         dy = display_height - (display_height % high);
780 
781     visible_items = dy / high;
782 
783     if (debug == True) {
784         fprintf(stderr, "Facts about window to open:\n"
785                 "  Window height: %d\n"
786                 "  Font   height: %d\n"
787                 "  Window rows  : %d\n",
788                 dy, high, visible_items);
789     }
790 
791     set_wm_hints(wide, dy, high);
792 
793     ask_wm_for_delete();
794 
795     /*
796      * Note: When mouse is disabled, we simply do not allow for any
797      * mouse-related X events to happen, here in this XSelectInput(),
798      * rather than modifying the Event test loop below.
799      */
800     XSelectInput(dpy, menuwin,
801         (mouse_on == True ? MenuMask : MenuMaskNoMouse));
802 
803     XMapWindow(dpy, menuwin);
804 
805 
806 
807     for (;;) {
808         /* BadWindow not mentioned in doc */
809         XNextEvent(dpy, &event);
810         if (debug == True)
811             fprintf(stderr, "X event: %s\n",
812                 event_names[event.type]); /* DEBUG thingy */
813         switch (event.type) {
814 
815             case EnterNotify:
816                 if (mousing == True) break;
817                 cur_store = cur;
818                 mousing = True;
819                 break;
820 
821             case LeaveNotify:
822                 if (mousing == False) break;
823                 /* fprintf(stderr, "going out\n"); */
824                 cur = cur_store;
825                 redraw_mouse(cur, high, wide);
826                 mousing = False;
827                 break;
828 
829             case ButtonRelease:
830                 if (mousing == False) break;
831                 if (event.xbutton.button == Button1) { /* left button */
832                     if (output == print) printf("%s\n", commands[cur]);
833                     else spawn(commands[cur]);
834                     return;
835                 } else if (event.xbutton.button == Button3) { /* right */
836                     if (prevmenu) {
837                         if (output == print) printf("%s\n", prevmenu);
838                         else spawn(prevmenu);
839                         return;
840                     }
841                 } else return;                   /* middle button */
842                 break;
843 
844 
845             case ButtonPress:
846             case MotionNotify:
847                 if (mousing == False) break;
848                 cur = event.xbutton.y / high + last_top;
849                 redraw_mouse(cur, high, wide);
850                 break;
851 
852 
853             case KeyPress:                     /* key is pressed in win */
854                 /* BadWindow not mentioned in doc */
855                 XLookupString(&event.xkey, NULL, 0, &key, NULL);
856                 switch (key) {
857                     /* case ????: (Ctrl-L for refresh)
858                         full_redraw = True;
859                         redraw(cur, high, wide);
860                         return 5
861                     */
862                     case XK_Escape:
863                     case XK_q:
864                         return;
865                         break;
866 
867                     case XK_Left:
868                     case XK_h:
869                         if (prevmenu) {
870                             if (output == print)
871                                 printf("%s\n", prevmenu);
872                             else
873                                 spawn(prevmenu);
874                             return;
875                         }
876                         break;
877 
878                     case XK_Right:
879                     case XK_Return:
880                     case XK_l:
881                         if (output == print)
882                             printf("%s\n", commands[cur]);
883                         else
884                             spawn(commands[cur]);
885                         return;
886                         break;
887 
888                     case XK_Tab:
889                     case XK_space:
890                     case XK_Down:
891                     case XK_j:
892                     case XK_plus:
893                         ++cur == numitems ? cur  = 0           : 0 ;
894                         cur_store = cur;
895                         redraw(cur, high, wide);
896                         break;
897 
898                     case XK_BackSpace:
899                     case XK_Up:
900                     case XK_k:
901                     case XK_minus:
902                         cur-- <=  0        ? cur = numitems - 1 : 0 ;
903                         cur_store = cur;
904                         redraw(cur, high, wide);
905                         break;
906 
907                     case XK_Home:
908                     case XK_Page_Up:
909                         cur = 0;
910                         cur_store = cur;
911                         redraw(cur, high, wide);
912                         break;
913 
914                     case XK_End:
915                     case XK_Page_Down:
916                         cur = numitems-1;
917                         cur_store = cur;
918                         redraw(cur, high, wide);
919                         break;
920 
921                 }
922                 break;
923 
924             case ConfigureNotify:              /* win is resized or moved */
925                 {
926                     int new_height = 0;
927                     XWindowAttributes gaga;
928                     if (!XGetWindowAttributes(dpy, menuwin, &gaga))
929                         break;
930                     new_height = gaga.height - (gaga.height % high);
931                     visible_items = new_height / high;
932                     cur_scroll_offset = (visible_items-1)/2;
933                     if (cur_scroll_offset > scroll_offset) {
934                         cur_scroll_offset = scroll_offset;
935                     }
936                     if (debug == True) {
937                         fprintf(stderr, "  Window resized/moved:\n"
938                             "    Window height (old/new):%4d /%4d\n"
939                             "    Visible rows  (old/new):%4d /%4d\n"
940                             "    Scroll offset (cmd/cur):%4d /%4d\n",
941                             gaga.height,      new_height,
942                             gaga.height/high, new_height/high,
943                             scroll_offset,    cur_scroll_offset);
944                     }
945                 }
946                 break;
947 
948             case UnmapNotify:                  /* win becomes hidden */
949                 if (unfocus_exit == True) {
950                     return;
951                 } else
952                     /* can generate BadWindow */
953                     XClearWindow(dpy, menuwin);
954                 break;
955 
956             /* FIXME: For some reason the code in `FocusIn' or `FocusOut' results in an
957              * "Error: BadWindow (invalid Window parameter)" message in Ratpoison
958              * (but it works, even if it look ugly).
959              */
960             case FocusIn: break;               /* win becomes focused again */
961             case FocusOut:                     /* win becomes unfocused   */
962                 if (unfocus_exit == True) {
963                     XDestroyWindow(dpy, menuwin);
964                     return;
965                 }
966                 break;
967 
968             case MapNotify:                    /* win becomes visible again */
969 
970                 {/* %%%% */
971                     XWindowAttributes gaga;
972                     int new_height = 0;
973                     if (XGetWindowAttributes(dpy, menuwin, &gaga)) {
974                         new_height = gaga.height - (gaga.height % high);
975                         if (gaga.height != new_height) {
976                             if (debug == True) {
977                                 fprintf(stderr, "RESIZING window to:\n"
978                                         "  Old  height: %d\n"
979                                         "  Font height: %d\n"
980                                         "  New  height: %d\n",
981                                         gaga.height, high, new_height);
982                             }
983                             /* FIXME window doesn't get resized, why not? */
984                             XResizeWindow(dpy, menuwin, 100, new_height);
985                             XSync(dpy, True);
986 
987                             if (XGetWindowAttributes(dpy, menuwin, &gaga)) {
988                                 if (debug == True) {
989                                     fprintf(stderr, "Window height after resizing: %d\n",
990                                             gaga.height);
991                                 }
992                             }
993                             visible_items = gaga.height / high;
994                             cur_scroll_offset = (visible_items-1)/2;
995                             if (cur_scroll_offset > scroll_offset) {
996                                 cur_scroll_offset = scroll_offset;
997                             } else
998                                 fprintf(stderr, "Shrinking scroll offset from %d to %d.\n",
999                                         scroll_offset, cur_scroll_offset);
1000                         }
1001                     } else {
1002                         fprintf(stderr, "can't get window attributes\n");
1003                     }
1004 
1005                     /* make sure window isn't taller than screen */
1006                     /* FIXME: problems when used inside a ratpoison frame */
1007 
1008                 }
1009             case Expose:                       /* win becomes partly covered */
1010                 full_redraw = True;
1011                 redraw(cur, high, wide);
1012                 /* `while' skips immediately subsequent Expose events in queue,
1013                  * which avoids superflous window redraws; taken from:
1014                  * www-h.eng.cam.ac.uk/help/tpl/graphics/X/X11R5/node31.html)*/
1015                 while (XCheckTypedWindowEvent(dpy,menuwin,Expose,&event))
1016                     ;
1017                 break;
1018 
1019             case ClientMessage:
1020                 cmsg = &event.xclient;
1021                 if (cmsg->message_type == wm_protocols
1022                         && cmsg->data.l[0] == wm_delete_window)
1023                     return;
1024         }
1025     }
1026 }
1027 
1028 
1029 /* set_wm_hints --- set all the window manager hints */
set_wm_hints(int wide,int high,int font_height)1030 void set_wm_hints(int wide, int high, int font_height) {
1031     XWMHints *wmhints;
1032     static XSizeHints sizehints = {
1033         USSize | PSize | PMinSize | PMaxSize | PBaseSize | PResizeInc,
1034         0, 0, 80, 80,                          /* x, y, width and height */
1035         1, 1,                                  /* min width and height */
1036         0, 0,                                  /* max width and height */
1037         1, 1,                                  /* width and height increments */
1038         {1, 1},                                /* x, y increments */
1039         {1, 1},                                /* aspect ratio - not used */
1040         0, 0,                                  /* base size */
1041         NorthWestGravity                       /* gravity */
1042     };
1043     XClassHint *classhints;
1044     XTextProperty wname;
1045 
1046     if ((wmhints = XAllocWMHints()) == NULL) {
1047         fprintf(stderr, "%s: cannot allocate window manager hints\n",
1048             progname);
1049         exit(1);
1050     }
1051 
1052     if ((classhints = XAllocClassHint()) == NULL) {
1053         fprintf(stderr, "%s: cannot allocate class hints\n",
1054             progname);
1055         exit(1);
1056     }
1057 
1058     /* window width */
1059     sizehints.width      = sizehints.base_width  =
1060     sizehints.min_width  = sizehints.max_width   = wide;
1061 
1062     /* window height */
1063     /* (always an even number of font heights) */
1064     sizehints.height     = sizehints.base_height = high;
1065     sizehints.min_height = sizehints.height_inc  = font_height;
1066     sizehints.max_height = numitems*font_height;
1067 
1068     if (XStringListToTextProperty(&titlename, 1, &wname) == 0) {
1069         fprintf(stderr, "%s: cannot allocate window name structure\n",
1070             progname);
1071         exit(1);
1072     }
1073 
1074     /* open menu window */
1075     menuwin = XCreateSimpleWindow(dpy, root, sizehints.x, sizehints.y,
1076                 sizehints.width, sizehints.height, 1, fg, bg);
1077 
1078     wmhints->input         = True;
1079     wmhints->initial_state = NormalState;
1080     wmhints->flags         = StateHint | InputHint;
1081 
1082     classhints->res_name   = progname;
1083     classhints->res_class  = "ratmen";
1084 
1085     XSetWMProperties(dpy, menuwin, &wname, NULL,
1086         g_argv, g_argc, &sizehints, wmhints, classhints);
1087 }
1088 
1089 /* ask_wm_for_delete --- jump through hoops to ask WM to delete us */
ask_wm_for_delete(void)1090 void ask_wm_for_delete(void) {
1091     int status;
1092 
1093     wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
1094     wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
1095     status = XSetWMProtocols(dpy, menuwin, &wm_delete_window, 1);
1096 
1097     if (status != True)
1098         fprintf(stderr, "%s: cannot ask for clean delete\n",
1099             progname);
1100 }
1101 
1102 
1103 /* redraw --- actually draw the menu */
redraw_snazzy(int cur_item,int high,int wide)1104 void redraw_snazzy (int cur_item, int high, int wide) {
1105     int i, j, ty, tx;
1106     if (cur_item < 0) cur_item = 0;    /* negative item number */
1107     XClearWindow(dpy, menuwin);
1108     for (i = 0, j = cur_item; i < numitems; i++, j++) {
1109         j %= numitems;
1110         if (align == right) {
1111             tx = wide - XTextWidth(font, labels[j], strlen(labels[j]));
1112         } else if (align == center) {
1113             tx = (wide - XTextWidth(font, labels[j], strlen(labels[j]))) / 2;
1114         } else { /* align == left */
1115             tx = 0;
1116         }
1117         ty = i*high + font->ascent + 1;
1118         XDrawString(dpy, menuwin, gc, tx, ty, labels[j], strlen(labels[j]));
1119     }
1120     XFillRectangle(dpy, menuwin, gc, 0, 0, wide, high);
1121 }
1122 
redraw_mouse(int cur_item,int high,int wide)1123 void redraw_mouse (int cur_item, int high, int wide) {
1124     if (cur_item <= last_top)
1125         cur_item = last_top; else
1126     if (cur_item > last_top+visible_items)
1127         cur_item = last_top+visible_items;
1128     if (cur_item == last_item) return;         /* no movement = no update */
1129     XFillRectangle(dpy, menuwin, gc, 0, (last_item - last_top) * high, wide, high);
1130     XFillRectangle(dpy, menuwin, gc, 0, (cur_item  - last_top) * high, wide, high);
1131     if (debug == True)
1132         fprintf(stderr, "current item: %d (of %d-%d)\n",
1133                 cur_item, last_top, last_top+visible_items);
1134     last_item = cur_item;
1135 }
1136 
1137 
redraw_dreary(int cur_item,int high,int wide)1138 void redraw_dreary (int cur_item, int high, int wide) {
1139     int i, j, ty, tx;
1140     int cur_top = last_top;                    /* local top var */
1141 
1142     if (cur_item == last_item && full_redraw != True)
1143         return;                                /* no movement = no update */
1144 
1145     /* change top item (i.e. scroll menu) */
1146     if (cur_item < last_top + cur_scroll_offset) { /* scroll upward */
1147         cur_top  = cur_item - cur_scroll_offset;
1148         if (cur_top < 0) cur_top = 0;          /* don't go above menu */
1149     } else if (                                /* scroll downward */
1150         cur_item > last_top - (cur_scroll_offset+1) + visible_items
1151     ) {
1152         cur_top  = cur_item + (cur_scroll_offset+1) - visible_items;
1153         if (cur_top > numitems - visible_items)/* don't go below menu */
1154             cur_top = numitems - visible_items;
1155     } else {
1156         if (cur_top < 0) cur_top = 0;          /* don't go above menu */
1157     }
1158 
1159 
1160     if (full_redraw == True || cur_top != last_top) {
1161         /* redraw all visible items */
1162         if (debug == True) fprintf(stderr, "  (full menu redraw)\n");
1163         XClearWindow(dpy, menuwin);
1164         for (i = 0; i < visible_items; i++) {
1165             j = cur_top + i;
1166             if (align == right) {
1167                 tx = wide - XTextWidth(font, labels[j], strlen(labels[j]));
1168             } else if (align == center) {
1169                 tx = (wide - XTextWidth(font, labels[j], strlen(labels[j]))) / 2;
1170             } else { /* align == left */
1171                 tx = 0;
1172             }
1173             ty = i * high + font->ascent + 1;
1174             XDrawString(dpy, menuwin, gc, tx, ty, labels[j], strlen(labels[j]));
1175         }
1176         if (cur_item >= 0)
1177             XFillRectangle(dpy, menuwin, gc, 0, (cur_item - cur_top) * high, wide, high);
1178         last_top    = cur_top;
1179         full_redraw = False;
1180     } else {
1181         /* only invert last & current item */
1182         if (debug == True) fprintf(stderr, "  (partial menu redraw)\n");
1183         j =  cur_item -  cur_top;
1184         i = last_item - last_top;
1185         XFillRectangle(dpy, menuwin, gc, 0, (last_item - cur_top) * high, wide, high);
1186         if (cur_item >= 0)
1187             XFillRectangle(dpy, menuwin, gc, 0, (cur_item  - cur_top) * high, wide, high);
1188     }
1189     last_item = cur_item;
1190 }
1191 
1192 /*
1193 void redraw_dreary (int cur, int high, int wide) {
1194     int i, ty, tx;
1195     XClearWindow(dpy, menuwin);
1196     for (i = 0; i < numitems; i++) {
1197         if (align == right) {
1198             tx = wide - XTextWidth(font, labels[i], strlen(labels[i]));
1199         } else if (align == center) {
1200             tx = (wide - XTextWidth(font, labels[i], strlen(labels[i]))) / 2;
1201         } else { |* align == left *|
1202             tx = 0;
1203         }
1204         ty = i*high + font->ascent + 1;
1205         XDrawString(dpy, menuwin, gc, tx, ty, labels[i], strlen(labels[i]));
1206     }
1207     XFillRectangle(dpy, menuwin, gc, 0, cur*high, wide, high);
1208 }
1209 */
1210 
1211 /*
1212 #manpage: ratmen(1)
1213 
1214 =head1 NAME
1215 
1216 ratmen - create a menu to run commands
1217 
1218 
1219 =head1 SYNOPSIS
1220 
1221 B<ratmen> [I<OPTION>]... I<MENUITEM> I<COMMAND>...
1222 
1223 B<ratmen> [I<OPTION>]... {B<-d>I<##>|B<--delimiter> I<##>} I<MENUITEM>I<##>I<COMMAND> ...
1224 
1225 
1226 
1227 =head1 DESCRIPTION
1228 
1229 B<Ratmen> is a simple program that accepts a list of MENUITEM and COMMAND pairs
1230 on the command line. It creates a window that consists of nothing but a menu.
1231 When a particular MENUITEM is selected by the user, the corresponding COMMAND
1232 is executed or (using B<--print>) printed on standard output.
1233 
1234 Menu items and commands may either be given separately (as in B<ratmenu>) using
1235 two arguments for each menu option, or, optionally you may specify a delimiter
1236 (using the B<--delimiter> option) and specify both menu item and command in the
1237 same argument (similar to B<9menu>). The delimiter may be of any length.
1238 
1239 If the command is omitted (or if an empty command is supplied when not using
1240 B<--delimiter>) then the menu item text will be used as the command.
1241 
1242 
1243 =head1 OPTIONS
1244 
1245 Some options may be specified using X resouces (allowing you to define some
1246 defaults you like in your in you F<~/.Xresources>, F<~/.Xdefaults> or similar).
1247 To make this mechanism as useful as possible I would suggest that you refrain
1248 from using such options on the command line, unless you really need to, and
1249 instead enable/disable the corresponding X resource setting.
1250 
1251 B<ratmen>'s default resource class is the name of the executed file, either
1252 B<ratmen> or, if the executable was called through a link, the name of the link
1253 in question. The command line option B<--class> may be used to override the
1254 default X resource class name.
1255 
1256 
1257 =over 8
1258 
1259 =item B<--align> I<{left|center|right}>  (X resource: B<align>)
1260 
1261 Aligns the text of the menu entries to the B<left>, B<center> or B<right>.
1262 Defaults to B<left>. (Short options B<-l>, B<-c> and B<-r> may also be used for
1263 B<left>, B<center> and B<right> respectivelly.)
1264 
1265 
1266 =item B<-b>, B<--back> I<PREVMENU>
1267 
1268 Run command PREVMENU when user goes back in the menu hierarchy. Useful when
1269 using nested menus; it gives the user a way to back out and return to the
1270 previous menu. Note that you can use this option for other things too. The
1271 command specified by the B<--back> option is executed when the user hits one of
1272 the "back" keys.
1273 
1274 
1275 =item B<--background> I<BGCOLOR>  (X resource: B<background>)
1276 
1277 Set the background color to BGCOLOR. By default, the background color is black.
1278 BGCOLOR may be the name of any color accepted by your X server.
1279 
1280 
1281 =item B<-C>, B<--class> I<CLASSNAME>
1282 
1283 This option allows you to override B<ratmen>'s resource class. Normally it is
1284 "ratmen", but it can be set to another class such as "ratmenu" to override
1285 selected resources.
1286 
1287 
1288 =item B<--debug>
1289 
1290 Makes B<ratmen> talk quite a lot on standard error.
1291 
1292 
1293 =item B<-d>, B<--delimiter> I<DELIM>
1294 
1295 This changes the behaviour when parsing subsequent menuitem/command pairs.
1296 
1297 Normally the command line argumens are taken to be alternately menu items and
1298 their related commands, thus requiring an even number of arguments to be passed
1299 to B<ratmen>. (If a command is given as "" it is taken to be the same as the
1300 menu item.) This behaviour can make it somewhat difficult to distinguish
1301 between menu item arguments and command arguments in cases where the menu grow
1302 quite big (e.g. in a script). Therefore an alternative is provided...
1303 
1304 If you specify a delimiter (using B<--delimiter>) this behaviour is changed and
1305 the menu item and command are both expected to occur in the same argument,
1306 separated by whatever delimiter you've specified. This makes the command line
1307 easier to read (for a human) but is sometimes disadvantageous, especially in
1308 autogenerated menus, since the delimiter in question cannot occur in the menu
1309 item text. (See also L<"EXAMPLES"> below.)
1310 
1311 
1312 =item B<-D>, B<--display> I<DISPLAYNAME>
1313 
1314 Use the X display DISPLAYNAME, instead of the default display. Normally you
1315 won't need to use this.
1316 
1317 
1318 =item B<--foreground> I<FGCOLOR>  (X resource: B<foreground>)
1319 
1320 Set the foreground color to FGCOLOR. By default, the foreground color is black.
1321 FGCOLOR may be the name of any color accepted by your X server.
1322 
1323 
1324 =item B<-F>, B<--font> I<FONTNAME>  (X resource: B<font>)
1325 
1326 Use the font FONTNAME instead of the default font.
1327 
1328 
1329 =item B<-h>, B<--help>
1330 
1331 Output a brief help message.
1332 
1333 =item B<--mouse>  (X resource: B<mouse>)
1334 
1335 =item B<--no-mouse>
1336 
1337 Enable/disable mouse support in B<ratmen>. The mouse support is quite limited
1338 (you can't scroll a large menu using the mouse) and is only intended for those
1339 moments when you instinctively want to click on something you see in order to
1340 select it. See also L<MOUSE SUPPORT>.
1341 
1342 Use C<ratmen.mouse: false> (or `no' or `off') in your X resource file to
1343 disable mouse support as a default, and C<ratmen.mouse: true> (or `yes' or `on)
1344 to enable it.
1345 
1346 
1347 =item B<-i>, B<--item> I<POSITION>
1348 
1349 Pre-select menu item number POSITION, instead of the first menu item, upon
1350 opening the menu. Menu items are numbered from 1. This is sometimes useful in
1351 scripts.
1352 
1353 If POSITION is 0, then no item is selected initially. Going up one item will
1354 make the menu jump to the last item, going down will jump to the first item.
1355 Pressing enter while no item is selected is the same as aborting. There is no
1356 way to zero entries, other than this, so if you move about you cannot return to
1357 the state where no item is selected (but why should you ever want that?).
1358 
1359 The described behavior is useful when the initially selected item denotes a
1360 `current' value (useful when called from a script) and the absence of a
1361 selected item may be used to indicate that there is no such `current' value.
1362 
1363 
1364 =item B<-p>, B<--print>
1365 
1366 Prints the COMMAND associated with the selected MENUITEM on standard output
1367 instead of running it.
1368 
1369 Using this option you can use a menu for all kinds of selections, and not only
1370 for running a program. When using this option COMMAND no longer need to be a
1371 valid command at all -- any string will work. See also L<"EXAMPLES"> below.
1372 
1373 
1374 =item B<-o>, B<--scroll-offset> I<ITEMS>  (X resource: B<scrollOffset>)
1375 
1376 If a menu is too large to fit in one window, it will become scrollable.
1377 B<Ratmen> will try to keep at least ITEMS number of items between the current
1378 position and the top or bottom of the menu. If you get closer than this, the
1379 menu will scroll. As you get close to the top or bottom of the menu scrolling
1380 will cease. (Default scroll offset is 3.)
1381 
1382 Scroll offset may not be larger than half of the menu. If it is it will be cut
1383 down to that value.
1384 
1385 
1386 =item B<-S>, B<--shell> I<PROG>
1387 
1388 Use I<PROG> as the shell to run commands, instead of B</bin/sh>. A popular
1389 alternative shell is rc(1). If the shell cannot be executed, B<ratmen> will
1390 silently fall back to using B</bin/sh>.
1391 
1392 
1393 =item B<-s>, B<--style> I<{snazzy|dreary}>  (X resource: B<style>)
1394 
1395 The default style is B<dreary>, where the highlight bar moves up and down the
1396 menu as it does on all conventional keyboard controlled menus.  In B<dreary>
1397 mode, the highlight bar, which shows the currently selected item, remains
1398 stationary while all the menu items are rotated up or down when the cursor keys
1399 are moved.
1400 
1401 
1402 =item B<-t>, B<--title> I<NAME>
1403 
1404 Change the title of the menu window to NAME. The default title is the last
1405 component of the path used to run B<ratmen>, typically, "ratmen".
1406 
1407 
1408 =item B<--unfocus-exit>  (X resource: B<unfocusExit>)
1409 
1410 =item B<--no-unfocus-exit>
1411 
1412 FIXME: Currently a `BadWindow' error message is generated by X when the current
1413 instance of B<ratmen> dies of unfocus. Does anyone know how to fix this?
1414 
1415 B<--unfocus-exit> causes B<ratmen> to die (without any option being selected)
1416 if its window loses focus (it's probably not a good to use in combination with
1417 a window manager that automatically focuses the window under your pointer).
1418 
1419 B<--no-unfocus-exit> makes B<ratmen> survive unfocusing. This can be confusing
1420 in some cases as it makes it possible to have start several menus at once
1421 (normally the previous menu would die from unfocus).
1422 
1423 Use C<ratmen.unfocusExit: false> (or `no' or `off') in your X resource file to
1424 disable unfocus deaths as a default, and C<ratmen.unfocusExit: true> (or `yes'
1425 or `on) to enable it.
1426 
1427 
1428 =item B<-V>, B<--version>
1429 
1430 This option prints the version of ratmen on the standard output, and then
1431 exits with  an exit value of zero.
1432 
1433 =back
1434 
1435 
1436 =head1 KEYSTROKES
1437 
1438 The B<Up> keystrokes move the selection to the next item up. The B<Down>
1439 keystrokes move the selection to the next item down. When the selection reaches
1440 the top or bottom, it scrolls around to the other side on pressing of the
1441 appropriate keystroke. The B<Select> keystrokes execute the command
1442 corresponding to the currently selected menu item, and exit ratmen. The
1443 B<Back> keystrokes does nothing unless the B<--back> option was used, in which
1444 case it will run the command specified by that option and exit ratmen. The
1445 B<Exit> keystrokes quit ratmen without doing anything.
1446 
1447     Up      "k", Up_arrow, BackSpace, "-"
1448     Down    "j", Down_arrow, Space, Tab, "+"
1449     Select  "l", Right_arrow, Return
1450     Back    "h", Left_arrow
1451     Exit    "q", Escape
1452 
1453 
1454 =head1 MOUSE SUPPORT
1455 
1456 B<Ratmen> implements limited support for the rodent, you may select an item
1457 (left), go to any previous menu, given by B<--back>, (right) or abort the menu
1458 (any other; usually middle). The mouse cannot be used to scroll the menu.
1459 
1460 Your rodent won't interfere with the normal operation. If you place the pointer
1461 on the menu by mistake, simply move it outside of the menu to restore the
1462 selection. You may, however, use the keys to manipulate an entry selected with
1463 the mouse, in this case the key based selection sticks.
1464 
1465 Actions are performed upon releasing a mouse butten. To cancel an action after
1466 the button has been pressed, move it outside the menu window and release the
1467 button (this goes for all the buttons).
1468 
1469 
1470 =head1 EXAMPLES
1471 
1472 How about creating a little remote shell menu? The B<ratmenu>ish approach would be
1473 
1474     ratmen --label Remotes xterm "" acme "rsh acme xterm" herman "rsh herman 9term" &
1475 
1476 and to do it the B<9menu> way, type something like
1477 
1478     ratmen --label Remotes -d: xterm "acme:rsh acme xterm" "herman:rsh herman 9term" &
1479 
1480 to do the trick. You could also make a menu containing some nice X programs to
1481 run. Like this:
1482 
1483     ratmen --label "X progs" ghostview "" xdvi "" xeyes "" xneko "" &
1484 
1485 Or like this:
1486 
1487     ratmen --label -d: "X progs" ghostview xdvi xeyes xneko &
1488 
1489 That last one is a bit easier on the eyes, don't you think? If you want, you
1490 can use the B<--back> to call an "earlier" menu, like this:
1491 
1492     ratmen --back ~/bin/mypreviousmenu "X Eyes" xeyes &
1493 
1494 If you'd like to use a menu from within a shell script (a similar technique may
1495 of course be employed from any other programming language, such as perl) you
1496 could use the following:
1497 
1498     choice=`ratmen -pd: Abort Retry Ignore`
1499 
1500 Now any of the options selected will be put into the environment variable
1501 `$choice' (note, though, that this may also be empty if the user cancelled the
1502 
1503 menu). Here B<-p> (or B<--print>) option is used to print the selected COMMAND
1504 to standard out instead of running it, and B<-d> (or B<--delimiter>) is used
1505 simply to avoid having to fill out the command line with a lot of ugly ""
1506 arguments. (You could, of course, replace the colon in the command line with
1507 any character that you don't use in the menu.)
1508 
1509 And here are some lines from my F<~/.Xresources> file, for those interested:
1510 
1511 ! ratmen
1512 ratmen*foreground:    yellow
1513 ratmen*font:          -adobe-courier-medium-r-normal-*-18-*-*-*-m-*-iso8859-1
1514 ratmen*unfocusExit:   true
1515 
1516 This makes my menus easily distinguishable (since not much else is yellow in my
1517 system configuration), easily readable (since I like courier) and doesn't
1518 clutty my screen too much in case I happen to forget about them and go about
1519 doing something else instead of choosing and item from the menu.
1520 
1521 
1522 =head1 SEE ALSO
1523 
1524 F</etc/X11/rgb.txt> where you may find the names of appropriate colours to use
1525 with the B<--background> and B<--forground> options and X resources.
1526 
1527 
1528 =head1 AUTHORS
1529 
1530 The initial idea for this program was by Arnold Robbins, after having worked
1531 with John Mackin's GWM Blit emulation. Matty Farrow wrote a version using
1532 libXg, from which some ideas were borrowed. This code was written by David
1533 Hogan and Arnold Robbins. Rich Salz motivated the B<-shell> option. Jonathan
1534 Walther modified this code to play nicely with the ratpoison window manager by
1535 removing handling of mouse events and iconification.
1536 
1537 Zrajm C Akfogh <ratmen-mail@klingonska.org> changed command line syntax into
1538 the more standard getopts, added scrolling capacity if menu is to large to fit
1539 all at once, added B<--delimiter>, B<--item>, B<--print>, B<--scroll-offset>
1540 and B<--unfocus-exit> options and X resource support (Yay! No need to specify
1541 those longish font-thingies on the command line any more!).
1542 
1543 The name `ratmen' is both an abbreviation of `ratmenu' (from which this program
1544 is heavily derived) and a reference to the fact that *I* don't have any
1545 religious reasons for not using the rodent (I like the keyboard, but I also
1546 like freedom of choice).
1547 
1548 
1549 =head1 FUTURE
1550 
1551 I have not activelly made any changes to this program for several years, though
1552 it was originally my intention to write a program that works both under X, and
1553 in the console.
1554 
1555 I later wrote B<termmen>, which much closer resembles my intentions for
1556 B<ratmen>, but unfortunately only works in the console or terminal (i.e. does
1557 not pop up a window of its own under X). Both B<termmen> and this program is
1558 available from L<http://zrajm.klingonska.org/programs/>.
1559 
1560 This program is written in C, which is not my native language, while B<Termmen>
1561 is written as a zsh script. If anyone would like to continue development of
1562 B<ratmen>, or a have a patch they'd like applied. Please feel free to send it
1563 to me at ratmen-mail@klingonska.org.
1564 
1565 
1566 =head1 HISTORY
1567 
1568 [2003-02-21, 12:47-16:18] Implemented `--item' for choosing initially selected
1569 item. Menu item and command is now given in the same string, separated with :,
1570 which makes for nicer error detection and lesser errors since it is a bit
1571 easier to keep track of what does which on the command line.
1572 
1573 [2003-02-22, 15:38-16:43]
1574 
1575 [2003-02-23, 02:19-02:36] Implemented `--delimiter' which now must be used to
1576 get the "menuitem:command" (as opposed to "menuitem" "command") behaviour.
1577 This, because I realised that when called from a script using an on-the-fly
1578 generated menu it can be quite tedious to make sure that the delimiter does
1579 not occur in the `menuitem' string (and thus fuck up the menu). Of course in
1580 handwritten menus the delimiter approach is easier to handle, hence I allow
1581 for both. Delimiter now also may be more than one character long.
1582 
1583 [2003-02-23, 19:08-20:30] Implemented B<--unfocus-exit> which exits the menu if
1584 it's window is unfocused.
1585 
1586 [2003-02-23, 21:12-21:22]
1587 
1588 [2003-02-24, 00:22-00:49]
1589 
1590 [2003-02-24, 11:38-17:47] Now reads some options regulating appearance and
1591 behaviour from X resources in addition to the command line.
1592 
1593 [2003-02-25, 18:47-22:20] Wrote POD. At last found a really good name for the
1594 product. The B<ratmen>.
1595 
1596 [2003-02-26, 01:38-09:15] Now reads command line option using getopt; thusly
1597 supports both --long-options and short ones (and bundles of short ones). Yay!
1598 Still haven't been able to get the B<--unfocus-exit> to work properly.
1599 
1600 [2003-02-26, 13:27-14:34] Fixxed B<--unfocus-exit> so that it now really makes
1601 the menu die of unfocus. Unfortunately, however a `BadWindow' message is
1602 generated by X when doing so, which looks quite ugly in B<ratpoison> (which
1603 faithfully reports the error). I simply haven't been able to figure out what
1604 command is causing this, but my best guess is that there's some unlucky X
1605 function still queued which tries to manipulate the window after it has been
1606 closed.
1607 
1608 [2003-03-03, 00:13-01:54] Added the B<--print> option, which can be extremely
1609 useful in scripts and other programs where you want to use a menu for some kind
1610 of input, rather than for a platform from which to fire off some program. Added
1611 L<"BUGS"> section below. Added the B<--debug> option which (as of now) dumps
1612 some info on the events (key/mouse clicks focusing etc.) intercepted from X.
1613 
1614 [2003-03-08, 11:21-11:29] Added a declaration of the subroutine `strcasecmp'
1615 and thus eliminating a compiler warning. Program worked flawlessy even before
1616 (and still does) but with less annoyance for me.. =|:-) The declaration thingy
1617 was found on the 'net by googling for "strcasetmp introduction". Made arguments
1618 to B<--align> and B<--style> case insensetive.
1619 
1620 [2003-03-08, 20:27-22:58] Found and killed a bug which made X resources
1621 override the command line options.
1622 
1623 [2003-03-09, 07:04-10:15] Totally fixed the bug mentioned in previous comment.
1624 Added short options B<-l>, B<-c> and B<-r> (for left, center and right aligning
1625 of the menu text), and changed B<--label> to B<--title>. Also changed the size
1626 of the menu window so that it will always be an even number of text lines
1627 (looks pretty ogly when only half or a third of the last item can be seen).
1628 Opening of window could be still more optimal though (the problem still arises
1629 when X refuses to open the window with the requested height, e.g. when one uses
1630 frames in ratpoison).
1631 
1632 [2003-03-10, 00:53-04:07] v2.0 - Optimized menu redrawing routine for the
1633 non-scrolling case (used to flicker quite nastily when moving down large menus
1634 fast). Also finally got around to add scrolling capacity for menus too large to
1635 fit all at once. Added the related B<--scroll-offset> option and X resource as
1636 well.
1637 
1638 [2003-03-10, 22:10-23:00] v2.0.1 - Begun fixxing window size bug (interrupted
1639 by Buffy - The Vampire Slayer).
1640 
1641 [2003-03-11, 00:31-04:25]
1642 
1643 [2003-03-12, 00:13-01:14] v2.0.2 - Fixed weird update/redraw bug thingy. Used
1644 to get a totally black empty menu sometimes. Bug probably introduced in last
1645 session, and now deceased.
1646 
1647 [2003-03-19, 10:48-12:38]
1648 
1649 [2003-03-19, 14:17-20:18] v2.1 - Wrote better handling of X resources, comlete
1650 with readable B<--debug> output and all.
1651 
1652 [2003-03-19, 22:40-09:05] v2.2 - Implemented mouse support. Made B<--item=0>
1653 special. Bugfix: Now opens a window of the correct size by telling X that it
1654 wants a window whose vertical size should be any number of pixels evenly
1655 dividable by the font height. (B<Rxvt> served as a source of inspiration for
1656 this feature.) Also added keys Home/PgUp for going to the first item in the
1657 menu and End/PgDn to go to the last. B<--class> now really works.
1658 
1659 [2003-03-24, 15:41-16:32] v2.2.1 - Added options B<--mouse> and B<--no-mouse>
1660 and the corresponding X resource B<ratmen.mouse> -- mouse is enabled by
1661 default. Removed the short options B<-u> and B<-U> (synonyms for
1662 B<--unfocus-exit> and B<--no-unfocus-exit>) as to not encourage the
1663 useage of those options on the command line (should be set using X resources).
1664 
1665 [2003-03-28, 11:29-11:32] v2.2.2 - Killed bug which didn't allow spaces to be
1666 used in font names (this little bugfix also makes it quite necessary not to end
1667 your X resources in any extraneous spaces).
1668 
1669 [2007-10-25, 11:23-14:13] v2.2.3 -- Bugfix. Changed a couple of latin-1
1670 characters 173 (soft hyphens) to the minus signs the should have been all the
1671 time. Added the "FUTURE" heading above. Rewrote the Makefile.
1672 
1673 
1674 =head1 BUGS
1675 
1676 =over 4
1677 
1678 =item o
1679 
1680 When no item is selected (i.e. on startup with -i0) and you hover the menu with
1681 the pointer, and then remove the pointer, the first item becomes selected.
1682 
1683 
1684 =item o
1685 
1686 The B<snazzy> mode. I'm probably going to remove it sometime in the future.
1687 B<I> don't use it and I don't see any reason why anybody else should want to
1688 either... (Especially not now that we got scrollable B<dreary> mode..)
1689 
1690 
1691 =item o
1692 
1693 Generates a `BadWindow' when dying of unfocus under the B<--unfocus-exit>
1694 option.
1695 
1696 This should be curable by means of XSetErrorHandler(), at least if it is not
1697 B<ratpoison> causing these things.
1698 
1699 
1700 =item o
1701 
1702 Memory leaks? There are probably several (me being totally new to C) but since
1703 the execution time should never really accumulate I haven't made it a priority
1704 to kill 'em. Please tell me if you find any.
1705 
1706 
1707 =item o
1708 
1709 When using B<--item=0> and B<--style=snazzy> one must press arrow down *twice*
1710 to select the first item on the menu. (I'll probably fix this by removing the
1711 `snazzy' mode.)
1712 
1713 =back
1714 
1715 
1716 =head1 TODO
1717 
1718 These are the things a want to do next in approximate order of priority:
1719 
1720 
1721 =over 4
1722 
1723 =item o
1724 
1725 Parsing of standard input (so one may use B<ratmen> as a magic number) for
1726 interpreting menu files. If this is to work, maybe some special command line
1727 argument parsing is needed? (I strongly suspect that magic number arguments are
1728 not passed in the same fashion, as if given on the command line..)
1729 
1730 
1731 =item o
1732 
1733 Option for outputting number of selected menu item (B<--print-number>/B<-P>
1734 maybe?).
1735 
1736 
1737 =item o
1738 
1739 Change B<--print> behaviour to include B<-d>: or something automatically (so
1740 that menu item text is default output).
1741 
1742 
1743 =item o
1744 
1745 User-configurable keys for up/down choose etc.
1746 
1747 =item o
1748 
1749 Menu shortcuts (defined in the menu) -- if autogenerated shortcuts are good
1750 enough, this might not be needed.
1751 
1752 
1753 =item o
1754 
1755 Automatically generated key shortcuts (E.g. numbers 0-9 for items 1-10, or
1756 letter 'a' letter to loop through all items begginning with an 'a', or "/" and
1757 "?" to do incremental forward/backward search...)
1758 
1759 
1760 =item o
1761 
1762 Multiple selection mode (space=select; enter=finalize) (introduce colors for
1763 marked entry and marked & selected entry).
1764 
1765 
1766 =item o
1767 
1768 Incremental search in menu (using bottom item space for input).
1769 
1770 
1771 =item o
1772 
1773 Replacing top/bottom item with arrow (or something) if scrollable in that
1774 direction.
1775 
1776 =back
1777 
1778 =cut
1779 
1780 */
1781