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