/* * ratmen.c * (Note: This program is fully documented using POD. * Try `pod2text ratmen.c', or use `pod2man' to generate a manpage.) * * * This program puts up a window that is just a menu, and executes * commands that correspond to the items selected. * * Initial idea: Arnold Robbins * Version using libXg: Matty Farrow (some ideas borrowed) * This code by: David Hogan and Arnold Robbins * * Copyright (c), Arnold Robbins and David Hogan * * Arnold Robbins * arnold@skeeve.atl.ga.us * October, 1994 * * Code added to cause pop-up (unIconify) to move menu to mouse. * Christopher Platt * platt@coos.dartmouth.edu * May, 1995 * * Said code moved to -teleport option, and -warp option added. * Arnold Robbins * June, 1995 * * Code added to allow --foreground and --background colors. * John M. O'Donnell * odonnell@stpaul.lampf.lanl.gov * April, 1997 * * Ratpoison windowmanager specific hacking; removed a lot of junk * and added keyboard functionality * Jonathan Walther * krooger@debian.org * September, 2001 * * Derived from ratmenu and 9menu. * Zrajm C Akfohg <zrajm@klingonska.org> * */#include <stdio.h>#include <fcntl.h>#include <signal.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <X11/keysym.h>#include <X11/X.h>#include <X11/Xlib.h>#include <X11/Xutil.h>#include <X11/Xatom.h>#include <X11/Xresource.h>#include <getopt.h>#define FONT "9x15bold"/*#define MenuMask (ExposureMask|StructureNotifyMask|KeyPressMask|FocusChangeMask)*/#define MenuMask (ButtonPressMask|ButtonReleaseMask\ |LeaveWindowMask|EnterWindowMask|PointerMotionMask|ButtonMotionMask\ |ExposureMask|StructureNotifyMask|KeyPressMask|FocusChangeMask)#define MenuMaskNoMouse (ExposureMask|StructureNotifyMask\ |KeyPressMask|FocusChangeMask)Display *dpy; /* lovely X stuff */int screen;Window root;int display_height; /* height of screen in pixels */Window menuwin;GC gc;unsigned long fg;unsigned long bg;Colormap dcmap;XColor color;XFontStruct *font;Atom wm_protocols;Atom wm_delete_window;int g_argc; /* for XSetWMProperties to use */char **g_argv;int savex, savey;Window savewindow;#define Undef -1char *progname; /* program name *//* menu structure */char **labels; /* list of menu labels */char **commands; /* list of menu commands */int numitems; /* total number of items */int visible_items; /* number of items visible *//* program determined settings */int last_item = -1; /* previously selected item */int last_top = -1; /* previous top item in menu */int full_redraw = True; /* redraw all menu items */int cur_scroll_offset = Undef;/* command line setting variables */enum { left, center, right } align = Undef; /* -l, -r, -c, --align WAY */char *prevmenu = NULL; /* -b, --back PREVMENU */char *bgcname = NULL; /* --background BGCOLOR */char *classname = NULL; /* -C, --class CLASSNAME */int debug = False; /* --debug */char *delimiter = NULL; /* -d, --delimiter DELIM */char *displayname; /* -D, --display DISPLAYNAME */char *fgcname = NULL; /* --foreground FGCOLOR */char *fontname = NULL; /* -F, --font FNAME */int cur_item = 0; /* -i, --item POSITION */int mouse_on = Undef; /* --mouse / --no-mouse */enum { print, execute } output = execute; /* -p, --print */int scroll_offset = Undef; /* -o, --scroll-offset ITEMS */char *shell = "/bin/sh"; /* -S, --shell SHELL */enum { dreary, snazzy } style = Undef; /* -s, --style STYLE */char *titlename = NULL; /* -t, --title NAME */int unfocus_exit = Undef; /* -u, --unfocus-exit *//* function prototypes */int strcasecmp(char*, char*); /* string comparison */void ask_wm_for_delete(void);void reap(int);void redraw_snazzy(int, int, int);void redraw_dreary(int, int, int);void redraw_mouse (int, int, int);void (*redraw) (int, int, int) = redraw_dreary;void run_menu(int);void set_wm_hints(int, int, int);void spawn(char*);void help(void);void version(void);/* * Event loop monitor thingy stolen from: (use option `--debug') * http://www-h.eng.cam.ac.uk/help/tpl/graphics/X/X11R5/node41.html */static char *event_names[] = { "", "", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify", "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify", "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify"};/* produce error message */void die(char *message) { fprintf(stderr, "%s: %s\n", progname, message); fprintf(stderr, "Try `%s --help' for more information.\n", progname); exit(1);}/* rewritten to use getopts by Zrajm */int args(int argc, char **argv) { int c; while (1) { static struct option long_options[] = { {"align", required_argument, 0, 'A'}, /* no shortopt */ {"back", required_argument, 0, 'b'}, {"background", required_argument, 0, 'B'}, /* no shortopt */ {"class", required_argument, 0, 'c'}, {"debug", no_argument, 0, 'X'}, /* no shortopt */ {"delimiter", required_argument, 0, 'd'}, {"display", required_argument, 0, 'D'}, {"foreground", required_argument, 0, 'E'}, /* no shortopt */ {"font", required_argument, 0, 'F'}, {"help", no_argument, 0, 'h'}, {"item", required_argument, 0, 'i'}, {"mouse", no_argument, 0, 'm'}, /* no shortopt */ {"no-mouse", no_argument, 0, 'M'}, /* no shortopt */ {"print", no_argument, 0, 'p'}, {"scroll-offset", required_argument, 0, 'o'}, {"shell", required_argument, 0, 'S'}, {"style", required_argument, 0, 's'}, {"title", required_argument, 0, 't'}, {"unfocus-exit", no_argument, 0, 'u'}, {"no-unfocus-exit", no_argument, 0, 'U'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; c = getopt_long(argc, argv, "b:cC:d:D:F:hi:lo:prS:s:t:V", long_options, &option_index); /* Detect the end of the options. */ if (c == -1) break; switch (c) { /*case 0: printf("gaga\n");*/ /* If this option set a flag, do nothing else now. */ /*if (long_options[option_index].flag != 0) break; printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break;*/ case 'A': /* --align {left|center|right} */ if (strcasecmp(optarg, "left") == 0) align = left; else if (strcasecmp(optarg, "center") == 0) align = center; else if (strcasecmp(optarg, "right") == 0) align = right; else { char buffer[200] = ""; sprintf(buffer, "unknown align argument `%s' " "(should be `left', `center' or `right')", optarg); die(buffer); } break; case 'b': /* -b, --back PREVMENU */ prevmenu = optarg; break; case 'B': /* --background BGCOLOR */ bgcname = optarg; break; case 'c': /* -c, --align=center */ align = center; break; case 'C': /* -C, --class CLASSNAME */ classname = optarg; break; case 'X': /* --debug */ debug = True; break; case 'd': /* -d, --delimiter DELIM */ delimiter = optarg; if (strcmp(delimiter, "") == 0) die("delimiter must be at least one character long"); break; case 'D': /* -D, --display DISPLAYNAME */ displayname = optarg; break; case 'F': /* -F, --font FNAME */ fontname = optarg; break; case 'E': /* --foreground FGCOLOR */ fgcname = optarg; break; case 'h': /* -h, --help */ help(); break; case 'i': /* -i, --item POSITION */ cur_item = atoi(optarg) - 1; break; case 'm': /* --mouse */ mouse_on = True; break; case 'M': /* --no-mouse */ mouse_on = False; break; case 'l': /* -l, --align=left */ align = left; break; case 'p': /* -p, --print */ output = print; break; case 'o': /* -o, --scroll-offset ITEMS */ scroll_offset = atoi(optarg); break; case 'r': /* -r, --align=right */ align = right; break; case 'S': /* -S, --shell SHELL */ shell = optarg; break; case 's': /* -s, --style {snazzy|dreary} */ if (strcasecmp(optarg, "dreary") == 0) style = dreary; else if (strcasecmp(optarg, "snazzy") == 0) style = snazzy; else { char buffer[200] = ""; sprintf(buffer, "unknown style argument `%s' " "(should be `snazzy' or `dreary')", optarg); die(buffer); } break; case 't': /* -t, --title NAME */ titlename = optarg; break; case 'u': /* -u, --unfocus-exit */ unfocus_exit = True; break; case 'U': /* -U, --no-unfocus-exit */ unfocus_exit = False; break; case 'V': /* -V, --version */ version(); break; case '?': /* getopt_long already printed an error message. */ break; default: abort(); } } return(optind);}char *xresource_if(int test, Display *dpy, char *progname, char *resource) { /* char *cut = ""; */ char *tmp = ""; if (test) { tmp = XGetDefault(dpy, progname, resource); if (tmp != NULL) { /* found resource */ /* if ((cut = strchr(tmp, ' '))) |* trunc at 1st space *| *cut++ = '\0'; */ if (debug == True) fprintf(stderr, " %s.%-12s: >%s<\n", progname, resource, tmp); return tmp; } else if (debug == True) /* no resource found */ fprintf(stderr, " %s.%-12s: [not defined in X resources]\n", progname, resource); } else if (debug == True) /* given on command line */ fprintf(stderr, " %s.%-12s: [overriden by command line]\n", progname, resource); return NULL;}/* completely written by Zrajm */void xresources(Display *dpy) { char *tmp = ""; if (debug == True) fprintf(stderr, "Reading X resources:\n"); /* align: {left|center|right} */ tmp = xresource_if((align == Undef), dpy, classname, "align"); if (tmp != NULL) { if (strcasecmp(tmp, "left" ) == 0) align = left; else if (strcasecmp(tmp, "center") == 0) align = center; else if (strcasecmp(tmp, "right" ) == 0) align = right; } /* background: BGCOLOR */ tmp = xresource_if((bgcname == NULL), dpy, classname, "background"); if (tmp != NULL) bgcname = tmp; /* font: FNAME */ tmp = xresource_if((fontname == NULL), dpy, classname, "font"); if (tmp != NULL) fontname = tmp; /* foreground: FGCOLOR */ tmp = xresource_if((fgcname == NULL), dpy, classname, "foreground"); if (tmp != NULL) fgcname = tmp; /* mouse: {on|yes|true|off|no|false} */ tmp = xresource_if((mouse_on == Undef), dpy, classname, "mouse"); if (tmp != NULL) { if (strcasecmp(tmp, "on" ) == 0) mouse_on = True; else if (strcasecmp(tmp, "yes") == 0) mouse_on = True; else if (strcasecmp(tmp, "true") == 0) mouse_on = True; else if (strcasecmp(tmp, "off") == 0) mouse_on = False; else if (strcasecmp(tmp, "no") == 0) mouse_on = False; else if (strcasecmp(tmp, "false") == 0) mouse_on = False; } /* style: {dreary|snazzy} */ tmp = xresource_if((style == Undef), dpy, classname, "style"); if (tmp != NULL) { if (strcasecmp(tmp, "dreary") == 0) style = dreary; else if (strcasecmp(tmp, "snazzy") == 0) style = snazzy; } /* scrollOffset: ITEMS */ tmp = xresource_if((scroll_offset == Undef), dpy, classname, "scrollOffset"); if (tmp != NULL) scroll_offset = atoi(tmp); /* unfocusExit: {on|yes|true|off|no|false} */ tmp = xresource_if((unfocus_exit == Undef), dpy, classname, "unfocusExit"); if (tmp != NULL) { if (strcasecmp(tmp, "on" ) == 0) unfocus_exit = True; else if (strcasecmp(tmp, "yes") == 0) unfocus_exit = True; else if (strcasecmp(tmp, "true") == 0) unfocus_exit = True; else if (strcasecmp(tmp, "off") == 0) unfocus_exit = False; else if (strcasecmp(tmp, "no") == 0) unfocus_exit = False; else if (strcasecmp(tmp, "false") == 0) unfocus_exit = False; }}/* completely written by Zrajm *|void xresources(Display *dpy) { XrmDatabase resourceDb = 0; XrmValue value; |* resource value container *| char *cp, *tmp; XrmInitialize(); if (!(XResourceManagerString(dpy))) return; |* get Xresource database *| resourceDb = XrmGetStringDatabase(XResourceManagerString(dpy)); |* align: {left|center|right} *| if (align == Undef && XrmGetResource(resourceDb, "ratmen*align", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; if (strcasecmp(tmp, "left" ) == 0) align = left; else if (strcasecmp(tmp, "center") == 0) align = center; else if (strcasecmp(tmp, "right" ) == 0) align = right; } |* background: BGCOLOR *| if (bgcname == NULL && XrmGetResource(resourceDb, "ratmen*background", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; if (tmp) bgcname = tmp; } |* font: FNAME *| if (fontname == NULL && XrmGetResource(resourceDb, "ratmen*font", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; if (tmp) fontname = tmp; } |* foreground: FGCOLOR *| if (fgcname == NULL && XrmGetResource(resourceDb, "ratmen*foreground", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; if (tmp) fgcname = tmp; } |* style: {dreary|snazzy} *| if (style == Undef && XrmGetResource(resourceDb, "ratmen*style", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; if (strcasecmp(tmp, "dreary") == 0) style = dreary; else if (strcasecmp(tmp, "snazzy") == 0) style = snazzy; } |* scrollOffset: ITEMS *| if (scroll_offset == Undef && XrmGetResource(resourceDb, "ratmen*scrollOffset", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; scroll_offset = atoi(tmp); } |* unfocusExit: {on|yes|true|off|no|false} *| if (unfocus_exit == Undef && XrmGetResource(resourceDb, "ratmen*unfocusExit", "", &tmp, &value) ) { tmp = value.addr; if ((cp = strchr(tmp, ' '))) *cp++ = '\0'; if (strcasecmp(tmp, "on" ) == 0) unfocus_exit = True; else if (strcasecmp(tmp, "yes") == 0) unfocus_exit = True; else if (strcasecmp(tmp, "true") == 0) unfocus_exit = True; else if (strcasecmp(tmp, "off") == 0) unfocus_exit = False; else if (strcasecmp(tmp, "no") == 0) unfocus_exit = False; else if (strcasecmp(tmp, "false") == 0) unfocus_exit = False; } XrmDestroyDatabase(resourceDb);}*/void items(int start, int count, char **arg) { int j; char *cut; if (delimiter) { /* using delimiter */ /* do things similar to `9menu' */ if (count-start < 1) die("not enough arguments"); numitems = count-start; labels = (char **) malloc(numitems*sizeof(char *)); commands = (char **) malloc(numitems*sizeof(char *)); if (commands == NULL || labels == NULL) die("cannot allocate memory for command and label arrays"); for (j = 0; j < numitems; j ++) { labels[j] = arg[start+j]; if ((cut = strstr(labels[j], delimiter)) != NULL) { *cut = '\0'; cut += strlen(delimiter); commands[j] = cut; } else commands[j] = labels[j]; } return; } else { /* without delimiter */ /* do as `ratmenu' does */ if ((count-start) % 2 != 0) die("not an even number of menu arguments"); if ((count-start) * 2 < 1) die("not enough arguments"); numitems = (count-start) / 2; labels = (char **) malloc(numitems*sizeof(char *)); commands = (char **) malloc(numitems*sizeof(char *)); if (commands == NULL || labels == NULL) die("cannot allocate memory for command and label arrays"); for (j=0; start < count; j ++) { labels[j] = arg[start++]; commands[j] = arg[start++]; if (strcmp(commands[j], "") == 0) commands[j] = labels[j]; } return; }}int HandleXError(Display *dpy, XErrorEvent *event) {/* gXErrorFlag = 1;*/ return 0;}/* main --- crack arguments, set up X stuff, run the main menu loop */int main(int argc, char **argv) { int i; char *cp; XGCValues gv; unsigned long mask; g_argc = argc; /* save command line args */ g_argv = argv; /* get program name (=default X resource class & title) */ if ((cp = strrchr(argv[0], '/'))) /* if argv[0] contains slash */ progname = ++cp; /* use all after that */ else /* otherwise */ progname = argv[0]; /* use argv[0] as is */ /* set defaults for non-resource options */ titlename = progname; /* default window title */ classname = progname; /* default X resource class */ i = args(argc, argv); /* process command line args */ items(i, argc, argv); /* process menu items */ dpy = XOpenDisplay(displayname); if (dpy == NULL) { fprintf(stderr, "%s: cannot open display", progname); if (displayname != NULL) fprintf(stderr, " %s", displayname); fprintf(stderr, "\n"); exit(1); } /* make it ignore BadWindow errors */ XSetErrorHandler(HandleXError); XFlush(dpy); /* use Xresource for undefined values */ xresources(dpy); /* defaults (for undefined cases) */ if (fontname == NULL) fontname = FONT; /* default font */ if (scroll_offset == Undef) /* default scroll offset */ scroll_offset = 3; if (mouse_on == Undef) /* mouse menu selection */ mouse_on = True; if (style == snazzy) { /* default display style */ redraw = redraw_snazzy; } else { redraw = redraw_dreary; } cur_scroll_offset = scroll_offset; screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); display_height = DisplayHeight(dpy, screen); dcmap = DefaultColormap (dpy, screen); if (fgcname == NULL || XParseColor(dpy, dcmap, fgcname, &color) == 0 || XAllocColor(dpy, dcmap, &color) == 0 ) { fg = WhitePixel(dpy, screen); } else { fg = color.pixel; } if (bgcname == NULL || XParseColor(dpy, dcmap, bgcname, &color) == 0 || XAllocColor(dpy, dcmap, &color) == 0 ) { bg = BlackPixel(dpy, screen); } else { bg = color.pixel; } if ((font = XLoadQueryFont(dpy, fontname)) == NULL) { fprintf(stderr, "%s: fatal: cannot load font `%s'\n", progname, fontname); exit(1); } gv.foreground = fg^bg; gv.background = bg; gv.font = font->fid; gv.function = GXxor; gv.line_width = 0; mask = GCForeground | GCBackground | GCFunction | GCFont | GCLineWidth; gc = XCreateGC(dpy, root, mask, &gv); signal(SIGCHLD, reap); run_menu(cur_item); /* XCloseDisplay() cannot generate BadWindow */ XCloseDisplay(dpy); exit(0);}/* spawn --- run a command */void spawn(char *com) { int pid; static char *sh_base = NULL; if (sh_base == NULL) { sh_base = strrchr(shell, '/'); if (sh_base != NULL) sh_base++; else sh_base = shell; } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: can't fork\n", progname); return; } else if (pid > 0) return; close(ConnectionNumber(dpy)); execl(shell, sh_base, "-c", com, NULL); _exit(1);}/* reap --- collect dead children */void reap(int s) { (void) wait((int *) NULL); signal(s, reap);}void version(void) { printf("%s (for Ratpoison) 2.2.2 (compiled 28 March 2003)\n" "Written by Arnold Robbins & David Hogan (1994-1995),\n" "John M. O'Donnell (1997), Jonathan Walther (2001) and\n" "Zrajm C Akfohg (2003).\n" "\n" "Copyright 1994-1995, 1997, 2001, 2003 by respective author.\n" "Distributed under the GNU Public License.\n", progname); exit(0);}/* usage --- print a usage message and die */void help(void) { printf("Usage: %s [OPTION]... MENUITEM COMMAND ...\n" " or: %s [OPTION]... {-d##|--delimiter ##} MENUITEM##COMMAND ...\n" "Create a simple menu in a separate window and run user selected command.\n" "\n" " -l, -c, -r, set window text alignment (Xresource)\n" " --align {left|center|right}\n" " -b, --back PREVMENU command to run on `back'\n" " --background BGCOLOR set background color (Xresource)\n" " -C, --class CLASSNAME CLASSNAME of Xresources to look for\n" " --debug enable debug output on stderr\n" " -d, --delimiter DELIM pair up each MENUITEM and COMMAND\n" " in one argument separated by DELIM\n" " -D, --display DISPLAYNAME open menu on named X display\n" " --foreground FGCOLOR set foreground color (Xresource)\n" " -F, --font FNAME Use X font FNAME (Xresource)\n" " -h, --help display this help and exit\n" " i, --item POSITION set MENUITEM selected on startup\n" " --mouse / --no-mouse enable/disable mouse support (Xresource)\n" " -p, --print print COMMAND on stdout, don't execute\n" " -o, --scroll-offset ITEMS border distance for scrolling (Xresource)\n" " -S, --shell SHELL SHELL in which to run commands\n" " -s, --style {snazzy|dreary} select menu style (Xresource)\n" " -t, --title NAME set window title to NAME\n" " --unfocus-exit / enable/disable exit on loosing window\n" " --no-unfocus-exit focus (Xresource)\n" " -V, --version output version information and exit\n" "\n", progname, progname); exit(0);}/* run_menu --- put up the window, execute selected commands */void run_menu(int cur) { KeySym key; XEvent event; XClientMessageEvent *cmsg; int i, wide, high, dx, dy, mousing = False, cur_store = cur; /* Currently selected menu item */ if (cur < -1) cur = 0; if (cur >= numitems) cur = numitems-1; /* find widest menu item */ dx = 0; for (i = 0; i < numitems; i++) { wide = XTextWidth(font, labels[i], strlen(labels[i])) + 4; if (wide > dx) dx = wide; } wide = dx; high = font->ascent + font->descent + 1; dy = numitems * high; /* height of menu window */ if (dy > display_height) /* shrink if outside screen */ dy = display_height - (display_height % high); visible_items = dy / high; if (debug == True) { fprintf(stderr, "Facts about window to open:\n" " Window height: %d\n" " Font height: %d\n" " Window rows : %d\n", dy, high, visible_items); } set_wm_hints(wide, dy, high); ask_wm_for_delete(); /* * Note: When mouse is disabled, we simply do not allow for any * mouse-related X events to happen, here in this XSelectInput(), * rather than modifying the Event test loop below. */ XSelectInput(dpy, menuwin, (mouse_on == True ? MenuMask : MenuMaskNoMouse)); XMapWindow(dpy, menuwin); for (;;) { /* BadWindow not mentioned in doc */ XNextEvent(dpy, &event); if (debug == True) fprintf(stderr, "X event: %s\n", event_names[event.type]); /* DEBUG thingy */ switch (event.type) { case EnterNotify: if (mousing == True) break; cur_store = cur; mousing = True; break; case LeaveNotify: if (mousing == False) break; fprintf(stderr, "going out\n"); cur = cur_store; redraw_mouse(cur, high, wide); mousing = False; break; case ButtonRelease: if (mousing == False) break; if (event.xbutton.button == Button1) { /* left button */ if (output == print) printf("%s\n", commands[cur]); else spawn(commands[cur]); return; } else if (event.xbutton.button == Button3) { /* right */ if (prevmenu) { if (output == print) printf("%s\n", prevmenu); else spawn(prevmenu); return; } } else return; /* middle button */ break; case ButtonPress: case MotionNotify: if (mousing == False) break; cur = event.xbutton.y / high + last_top; redraw_mouse(cur, high, wide); break; case KeyPress: /* key is pressed in win */ /* BadWindow not mentioned in doc */ XLookupString(&event.xkey, NULL, 0, &key, NULL); switch (key) { /* case ????: (Ctrl-L for refresh) full_redraw = True; redraw(cur, high, wide); return 5 */ case XK_Escape: case XK_q: return; break; case XK_Left: case XK_h: if (prevmenu) { if (output == print) printf("%s\n", prevmenu); else spawn(prevmenu); return; } break; case XK_Right: case XK_Return: case XK_l: if (output == print) printf("%s\n", commands[cur]); else spawn(commands[cur]); return; break; case XK_Tab: case XK_space: case XK_Down: case XK_j: case XK_plus: ++cur == numitems ? cur = 0 : 0 ; cur_store = cur; redraw(cur, high, wide); break; case XK_BackSpace: case XK_Up: case XK_k: case XK_minus: cur-- <= 0 ? cur = numitems - 1 : 0 ; cur_store = cur; redraw(cur, high, wide); break; case XK_Home: case XK_Page_Up: cur = 0; cur_store = cur; redraw(cur, high, wide); break; case XK_End: case XK_Page_Down: cur = numitems-1; cur_store = cur; redraw(cur, high, wide); break; } break; case ConfigureNotify: /* win is resized or moved */ { int new_height = 0; XWindowAttributes gaga; if (!XGetWindowAttributes(dpy, menuwin, &gaga)) break; new_height = gaga.height - (gaga.height % high); visible_items = new_height / high; cur_scroll_offset = (visible_items-1)/2; if (cur_scroll_offset > scroll_offset) { cur_scroll_offset = scroll_offset; } if (debug == True) { fprintf(stderr, " Window resized/moved:\n" " Window height (old/new):%4d /%4d\n" " Visible rows (old/new):%4d /%4d\n" " Scroll offset (cmd/cur):%4d /%4d\n", gaga.height, new_height, gaga.height/high, new_height/high, scroll_offset, cur_scroll_offset); } } break; case UnmapNotify: /* win becomes hidden */ if (unfocus_exit == True) { return; } else /* can generate BadWindow */ XClearWindow(dpy, menuwin); break; /* FIXME: For some reason the code in `FocusIn' or `FocusOut' results in an * "Error: BadWindow (invalid Window parameter)" message in Ratpoison * (but it works, even if it look ugly). */ case FocusIn: break; /* win becomes focused again */ case FocusOut: /* win becomes unfocused */ if (unfocus_exit == True) { XDestroyWindow(dpy, menuwin); return; } break; case MapNotify: /* win becomes visible again */ {/* %%%% */ XWindowAttributes gaga; int new_height = 0; if (XGetWindowAttributes(dpy, menuwin, &gaga)) { new_height = gaga.height - (gaga.height % high); if (gaga.height != new_height) { if (debug == True) { fprintf(stderr, "RESIZING window to:\n" " Old height: %d\n" " Font height: %d\n" " New height: %d\n", gaga.height, high, new_height); } /* FIXME window doesn't get resized, why not? */ XResizeWindow(dpy, menuwin, 100, new_height); XSync(dpy, True); if (XGetWindowAttributes(dpy, menuwin, &gaga)) { if (debug == True) { fprintf(stderr, "Window height after resizing: %d\n", gaga.height); } } visible_items = gaga.height / high; cur_scroll_offset = (visible_items-1)/2; if (cur_scroll_offset > scroll_offset) { cur_scroll_offset = scroll_offset; } else fprintf(stderr, "Shrinking scroll offset from %d to %d.\n", scroll_offset, cur_scroll_offset); } } else { fprintf(stderr, "can't get window attributes\n"); } /* make sure window isn't taller than screen */ /* FIXME: problems when used inside a ratpoison frame */ } case Expose: /* win becomes partly covered */ full_redraw = True; redraw(cur, high, wide); /* `while' skips immediately subsequent Expose events in queue, * which avoids superflous window redraws; taken from: * www-h.eng.cam.ac.uk/help/tpl/graphics/X/X11R5/node31.html)*/ while (XCheckTypedWindowEvent(dpy,menuwin,Expose,&event)) ; break; case ClientMessage: cmsg = &event.xclient; if (cmsg->message_type == wm_protocols && cmsg->data.l[0] == wm_delete_window) return; } }}/* set_wm_hints --- set all the window manager hints */void set_wm_hints(int wide, int high, int font_height) { XWMHints *wmhints; static XSizeHints sizehints = { USSize | PSize | PMinSize | PMaxSize | PBaseSize | PResizeInc, 0, 0, 80, 80, /* x, y, width and height */ 1, 1, /* min width and height */ 0, 0, /* max width and height */ 1, 1, /* width and height increments */ {1, 1}, /* x, y increments */ {1, 1}, /* aspect ratio - not used */ 0, 0, /* base size */ NorthWestGravity /* gravity */ }; XClassHint *classhints; XTextProperty wname; if ((wmhints = XAllocWMHints()) == NULL) { fprintf(stderr, "%s: cannot allocate window manager hints\n", progname); exit(1); } if ((classhints = XAllocClassHint()) == NULL) { fprintf(stderr, "%s: cannot allocate class hints\n", progname); exit(1); } /* window width */ sizehints.width = sizehints.base_width = sizehints.min_width = sizehints.max_width = wide; /* window height */ /* (always an even number of font heights) */ sizehints.height = sizehints.base_height = high; sizehints.min_height = sizehints.height_inc = font_height; sizehints.max_height = numitems*font_height; if (XStringListToTextProperty(&titlename, 1, &wname) == 0) { fprintf(stderr, "%s: cannot allocate window name structure\n", progname); exit(1); } /* open menu window */ menuwin = XCreateSimpleWindow(dpy, root, sizehints.x, sizehints.y, sizehints.width, sizehints.height, 1, fg, bg); wmhints->input = True; wmhints->initial_state = NormalState; wmhints->flags = StateHint | InputHint; classhints->res_name = progname; classhints->res_class = "ratmen"; XSetWMProperties(dpy, menuwin, &wname, NULL, g_argv, g_argc, &sizehints, wmhints, classhints);}/* ask_wm_for_delete --- jump through hoops to ask WM to delete us */void ask_wm_for_delete(void) { int status; wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False); status = XSetWMProtocols(dpy, menuwin, &wm_delete_window, 1); if (status != True) fprintf(stderr, "%s: cannot ask for clean delete\n", progname);}/* redraw --- actually draw the menu */void redraw_snazzy (int cur_item, int high, int wide) { int i, j, ty, tx; if (cur_item < 0) cur_item = 0; /* negative item number */ XClearWindow(dpy, menuwin); for (i = 0, j = cur_item; i < numitems; i++, j++) { j %= numitems; if (align == right) { tx = wide - XTextWidth(font, labels[j], strlen(labels[j])); } else if (align == center) { tx = (wide - XTextWidth(font, labels[j], strlen(labels[j]))) / 2; } else { /* align == left */ tx = 0; } ty = i*high + font->ascent + 1; XDrawString(dpy, menuwin, gc, tx, ty, labels[j], strlen(labels[j])); } XFillRectangle(dpy, menuwin, gc, 0, 0, wide, high);}void redraw_mouse (int cur_item, int high, int wide) { if (cur_item <= last_top) cur_item = last_top; else if (cur_item > last_top+visible_items) cur_item = last_top+visible_items; if (cur_item == last_item) return; /* no movement = no update */ XFillRectangle(dpy, menuwin, gc, 0, (last_item - last_top) * high, wide, high); XFillRectangle(dpy, menuwin, gc, 0, (cur_item - last_top) * high, wide, high); if (debug == True) fprintf(stderr, "current item: %d (of %d-%d)\n", cur_item, last_top, last_top+visible_items); last_item = cur_item;}void redraw_dreary (int cur_item, int high, int wide) { int i, j, ty, tx; int cur_top = last_top; /* local top var */ if (cur_item == last_item && full_redraw != True) return; /* no movement = no update */ /* change top item (i.e. scroll menu) */ if (cur_item < last_top + cur_scroll_offset) { /* scroll upward */ cur_top = cur_item - cur_scroll_offset; if (cur_top < 0) cur_top = 0; /* don't go above menu */ } else if ( /* scroll downward */ cur_item > last_top - (cur_scroll_offset+1) + visible_items ) { cur_top = cur_item + (cur_scroll_offset+1) - visible_items; if (cur_top > numitems - visible_items)/* don't go below menu */ cur_top = numitems - visible_items; } else { if (cur_top < 0) cur_top = 0; /* don't go above menu */ } if (full_redraw == True || cur_top != last_top) { /* redraw all visible items */ if (debug == True) fprintf(stderr, " (full menu redraw)\n"); XClearWindow(dpy, menuwin); for (i = 0; i < visible_items; i++) { j = cur_top + i; if (align == right) { tx = wide - XTextWidth(font, labels[j], strlen(labels[j])); } else if (align == center) { tx = (wide - XTextWidth(font, labels[j], strlen(labels[j]))) / 2; } else { /* align == left */ tx = 0; } ty = i * high + font->ascent + 1; XDrawString(dpy, menuwin, gc, tx, ty, labels[j], strlen(labels[j])); } if (cur_item >= 0) XFillRectangle(dpy, menuwin, gc, 0, (cur_item - cur_top) * high, wide, high); last_top = cur_top; full_redraw = False; } else { /* only invert last & current item */ if (debug == True) fprintf(stderr, " (partial menu redraw)\n"); j = cur_item - cur_top; i = last_item - last_top; XFillRectangle(dpy, menuwin, gc, 0, (last_item - cur_top) * high, wide, high); if (cur_item >= 0) XFillRectangle(dpy, menuwin, gc, 0, (cur_item - cur_top) * high, wide, high); } last_item = cur_item;}/*void redraw_dreary (int cur, int high, int wide) { int i, ty, tx; XClearWindow(dpy, menuwin); for (i = 0; i < numitems; i++) { if (align == right) { tx = wide - XTextWidth(font, labels[i], strlen(labels[i])); } else if (align == center) { tx = (wide - XTextWidth(font, labels[i], strlen(labels[i]))) / 2; } else { |* align == left *| tx = 0; } ty = i*high + font->ascent + 1; XDrawString(dpy, menuwin, gc, tx, ty, labels[i], strlen(labels[i])); } XFillRectangle(dpy, menuwin, gc, 0, cur*high, wide, high);}*//*#manpage: ratmen(1)=head1 NAMEratmen - create a menu to run commands=head1 SYNOPSISB<ratmen> [I<OPTION>]... I<MENUITEM> I<COMMAND>...B<ratmen> [I<OPTION>]... {B<-d>I<##>|B<--delimiter> I<##>} I<MENUITEM>I<##>I<COMMAND> ...=head1 DESCRIPTIONB<Ratmen> is a simple program that accepts a list of MENUITEM and COMMAND pairson the command line. It creates a window that consists of nothing but a menu.When a particular MENUITEM is selected by the user, the corresponding COMMANDis executed or (using B<--print>) printed on standard output.Menu items and commands may either be given separately (as in B<ratmenu>) usingtwo arguments for each menu option, or, optionally you may specify a delimiter(using the B<--delimiter> option) and specify both menu item and command in thesame argument (similar to B<9menu>). The delimiter may be of any length.If the command is omitted (or if an empty command is supplied when not usingB<--delimiter>) then the menu item text will be used as the command.=head1 OPTIONSSome options may be specified using X resouces (allowing you to define somedefaults you like in your in you F<~/.Xresources>, F<~/.Xdefaults> or similar).To make this mechanism as useful as possible I would suggest that you refrainfrom using such options on the command line, unless you really need to, andinstead enable/disable the corresponding X resource setting.B<ratmen>'s default resource class is the name of the executed file, eitherB<ratmen> or, if the executable was called through a link, the name of the linkin question. The command line option B<--class> may be used to override thedefault X resource class name.=over 8=item B<--align> I<{left|center|right}> (X resource: B<align>)Aligns the text of the menu entries to the B<left>, B<center> or B<right>.Defaults to B<left>. (Short options B<-l>, B<-c> and B<-r> may also be used forB<left>, B<center> and B<right> respectivelly.)=item B<-b>, B<--back> I<PREVMENU>Run command PREVMENU when user goes back in the menu hierarchy. Useful whenusing nested menus; it gives the user a way to back out and return to theprevious menu. Note that you can use this option for other things too. Thecommand specified by the B<--back> option is executed when the user hits one ofthe "back" keys.=item B<--background> I<BGCOLOR> (X resource: B<background>)Set the background color to BGCOLOR. By default, the background color is black.BGCOLOR may be the name of any color accepted by your X server.=item B<-C>, B<--class> I<CLASSNAME>This option allows you to override B<ratmen>'s resource class. Normally it is"ratmen", but it can be set to another class such as "ratmenu" to overrideselected resources.=item B<--debug>Makes B<ratmen> talk quite a lot on standard error.=item B<-d>, B<--delimiter> I<DELIM>This changes the behaviour when parsing subsequent menuitem/command pairs.Normally the command line argumens are taken to be alternately menu items andtheir related commands, thus requiring an even number of arguments to be passedto B<ratmen>. (If a command is given as "" it is taken to be the same as themenu item.) This behaviour can make it somewhat difficult to distinguishbetween menu item arguments and command arguments in cases where the menu growquite big (e.g. in a script). Therefore an alternative is provided...If you specify a delimiter (using B<--delimiter>) this behaviour is changed andthe menu item and command are both expected to occur in the same argument,separated by whatever delimiter you've specified. This makes the command lineeasier to read (for a human) but is sometimes disadvantageous, especially inautogenerated menus, since the delimiter in question cannot occur in the menuitem text. (See also L<"EXAMPLES"> below.)=item B<-D>, B<--display> I<DISPLAYNAME>Use the X display DISPLAYNAME, instead of the default display. Normally youwon't need to use this.=item B<--foreground> I<FGCOLOR> (X resource: B<foreground>)Set the foreground color to FGCOLOR. By default, the foreground color is black.FGCOLOR may be the name of any color accepted by your X server.=item B<-F>, B<--font> I<FONTNAME> (X resource: B<font>)Use the font FONTNAME instead of the default font.=item B<-h>, B<--help>Output a brief help message.=item B<--mouse> (X resource: B<mouse>)=item B<--no-mouse>Enable/disable mouse support in B<ratmen>. The mouse support is quite limited(you can't scroll a large menu using the mouse) and is only intended for thosemoments when you instinctively want to click on something you see in order toselect it. See also L<MOUSE SUPPORT>.Use C<ratmen.mouse: false> (or `no' or `off') in your X resource file todisable mouse support as a default, and C<ratmen.mouse: true> (or `yes' or `on)to enable it.=item B<-i>, B<--item> I<POSITION>Pre-select menu item number POSITION, instead of the first menu item, uponopening the menu. Menu items are numbered from 1. This is sometimes useful inscripts.If POSITION is 0, then no item is selected initially. Going up one item willmake the menu jump to the last item, going down will jump to the first item.Pressing enter while no item is selected is the same as aborting. There is noway to zero entries, other than this, so if you move about you cannot return tothe state where no item is selected (but why should you ever want that?).The described behavior is useful when the initially selected item denotes a`current' value (useful when called from a script) and the absence of aselected item may be used to indicate that there is no such `current' value.=item B<-p>, B<--print>Prints the COMMAND associated with the selected MENUITEM on standard outputinstead of running it.Using this option you can use a menu for all kinds of selections, and not onlyfor running a program. When using this option COMMAND no longer need to be avalid command at all -- any string will work. See also L<"EXAMPLES"> below.=item B<-o>, B<--scroll-offset> I<ITEMS> (X resource: B<scrollOffset>)If a menu is too large to fit in one window, it will become scrollable.B<Ratmen> will try to keep at least ITEMS number of items between the currentposition and the top or bottom of the menu. If you get closer than this, themenu will scroll. As you get close to the top or bottom of the menu scrollingwill cease. (Default scroll offset is 3.)Scroll offset may not be larger than half of the menu. If it is it will be cutdown to that value.=item B<-S>, B<--shell> I<PROG>Use I<PROG> as the shell to run commands, instead of B</bin/sh>. A popularalternative shell is rc(1). If the shell cannot be executed, B<ratmen> willsilently fall back to using B</bin/sh>.=item B<-s>, B<--style> I<{snazzy|dreary}> (X resource: B<style>)The default style is B<dreary>, where the highlight bar moves up and down themenu as it does on all conventional keyboard controlled menus. In B<dreary>mode, the highlight bar, which shows the currently selected item, remainsstationary while all the menu items are rotated up or down when the cursor keysare moved.=item B<-t>, B<--title> I<NAME>Change the title of the menu window to NAME. The default title is the lastcomponent of the path used to run B<ratmen>, typically, "ratmen".=item B<--unfocus-exit> (X resource: B<unfocusExit>)=item B<--no-unfocus-exit>FIXME: Currently a `BadWindow' error message is generated by X when the currentinstance of B<ratmen> dies of unfocus. Does anyone know how to fix this?B<--unfocus-exit> causes B<ratmen> to die (without any option being selected)if its window loses focus (it's probably not a good to use in combination witha window manager that automatically focuses the window under your pointer).B<--no-unfocus-exit> makes B<ratmen> survive unfocusing. This can be confusingin some cases as it makes it possible to have start several menus at once(normally the previous menu would die from unfocus).Use C<ratmen.unfocusExit: false> (or `no' or `off') in your X resource file todisable unfocus deaths as a default, and C<ratmen.unfocusExit: true> (or `yes'or `on) to enable it.=item B<-V>, B<--version>This option prints the version of ratmen on the standard output, and thenexits with an exit value of zero.=back=head1 KEYSTROKESThe B<Up> keystrokes move the selection to the next item up. The B<Down>keystrokes move the selection to the next item down. When the selection reachesthe top or bottom, it scrolls around to the other side on pressing of theappropriate keystroke. The B<Select> keystrokes execute the commandcorresponding to the currently selected menu item, and exit ratmen. TheB<Back> keystrokes does nothing unless the B<--back> option was used, in whichcase it will run the command specified by that option and exit ratmen. TheB<Exit> keystrokes quit ratmen without doing anything. Up "k", Up_arrow, BackSpace, "-" Down "j", Down_arrow, Space, Tab, "+" Select "l", Right_arrow, Return Back "h", Left_arrow Exit "q", Escape=head1 MOUSE SUPPORTB<Ratmen> implements limited support for the rodent, you may select an item(left), go to any previous menu, given by B<--back>, (right) or abort the menu(any other; usually middle). The mouse cannot be used to scroll the menu.Your rodent won't interfere with the normal operation. If you place the pointeron the menu by mistake, simply move it outside of the menu to restore theselection. You may, however, use the keys to manipulate an entry selected withthe mouse, in this case the key based selection sticks.Actions are performed upon releasing a mouse butten. To cancel an action afterthe button has been pressed, move it outside the menu window and release thebutton (this goes for all the buttons).=head1 EXAMPLESHow about creating a little remote shell menu? The B<ratmenu>ish approach would be ratmen --label Remotes xterm "" acme "rsh acme xterm" herman "rsh herman 9term" &and to do it the B<9menu> way, type something like ratmen --label Remotes -d: xterm "acme:rsh acme xterm" "herman:rsh herman 9term" &to do the trick. You could also make a menu containing some nice X programs torun. Like this: ratmen --label "X progs" ghostview "" xdvi "" xeyes "" xneko "" &Or like this: ratmen --label -d: "X progs" ghostview xdvi xeyes xneko &That last one is a bit easier on the eyes, don't you think? If you want, youcan use the B<--back> to call an "earlier" menu, like this: ratmen --back ~/bin/mypreviousmenu "X Eyes" xeyes &If you'd like to use a menu from within a shell script (a similar technique mayof course be employed from any other programming language, such as perl) youcould use the following: choice=`ratmen -pd: Abort Retry Ignore`Now any of the options selected will be put into the environment variable`$choice' (note, though, that this may also be empty if the user cancelled themenu). Here B<-p> (or B<--print>) option is used to print the selected COMMANDto standard out instead of running it, and B<-d> (or B<--delimiter>) is usedsimply to avoid having to fill out the command line with a lot of ugly ""arguments. (You could, of course, replace the colon in the command line withany character that you don't use in the menu.)And here are some lines from my F<~/.Xresources> file, for those interested:! ratmenratmen*foreground: yellowratmen*font: -adobe-courier-medium-r-normal-*-18-*-*-*-m-*-iso8859-1ratmen*unfocusExit: trueThis makes my menus easily distinguishable (since not much else is yellow in mysystem configuration), easily readable (since I like courier) and doesn'tclutty my screen too much in case I happen to forget about them and go aboutdoing something else instead of choosing and item from the menu.=head1 SEE ALSOF</etc/X11/rgb.txt> where you may find the names of appropriate colours to usewith the B<--background> and B<--forground> options and X resources.=head1 AUTHORSThe initial idea for this program was by Arnold Robbins, after having workedwith John Mackin's GWM Blit emulation. Matty Farrow wrote a version usinglibXg, from which some ideas were borrowed. This code was written by DavidHogan and Arnold Robbins. Rich Salz motivated the B<-shell> option. JonathanWalther modified this code to play nicely with the ratpoison window manager byremoving handling of mouse events and iconification.Zrajm C Akfogh <zrajm@klingonska.org> changed command line syntax into the morestandard getopts, added scrolling capacity if menu is to large to fit all atonce, added B<--delimiter>, B<--item>, B<--print>, B<--scroll-offset> andB<--unfocus-exit> options and X resource support (Yay! No need to specify thoselongish font-thingies on the command line any more!).The name `ratmen' is both an abbreviation of `ratmenu' (from which this programis heavily derived) and a reference to the fact that *I* don't have anyreligious reasons for not using the rodent (I like the keyboard, but I alsolike freedom of choice).=head1 HISTORY[2003-02-21, 12.47-16.18] Implemented `--item' for choosing initially selecteditem. Menu item and command is now given in the same string, separated with :,which makes for nicer error detection and lesser errors since it is a biteasier to keep track of what does which on the command line.[2003-02-22, 15.38-16.43][2003-02-23, 02.19-02.36] Implemented `--delimiter' which now must be used toget the "menuitem:command" (as opposed to "menuitem" "command") behaviour.This, because I realised that when called from a script using an on-the-flygenerated menu it can be quite tedious to make sure that the delimiter doesnot occur in the `menuitem' string (and thus fuck up the menu). Of course inhandwritten menus the delimiter approach is easier to handle, hence I allowfor both. Delimiter now also may be more than one character long.[2003-02-23, 19.08-20.30] Implemented B<--unfocus-exit> which exits the menu ifit's window is unfocused.[2003-02-23, 21.12-21.22][2003-02-24, 00.22-00.49][2003-02-24, 11.38-17.47] Now reads some options regulating appearance andbehaviour from X resources in addition to the command line.[2003-02-25, 18.47-22.20] Wrote POD. At last found a really good name for theproduct. The B<ratmen>.[2003-02-26, 01.38-09.15] Now reads command line option using getopt; thuslysupports both --long-options and short ones (and bundles of short ones). Yay!Still haven't been able to get the B<--unfocus-exit> to work properly.[2003-02-26, 13.27-14.34] Fixxed B<--unfocus-exit> so that it now really makesthe menu die of unfocus. Unfortunately, however a `BadWindow' message isgenerated by X when doing so, which looks quite ugly in B<ratpoison> (whichfaithfully reports the error). I simply haven't been able to figure out whatcommand is causing this, but my best guess is that there's some unlucky Xfunction still queued which tries to manipulate the window after it has beenclosed.[2003-03-03, 00.13-01.54] Added the B<--print> option, which can be extremelyuseful in scripts and other programs where you want to use a menu for some kindof input, rather than for a platform from which to fire off some program. AddedL<"BUGS"> section below. Added the B<--debug> option which (as of now) dumpssome info on the events (key/mouse clicks focusing etc.) intercepted from X.[2003-03-08, 11.21-11.29] Added a declaration of the subroutine `strcasecmp'and thus eliminating a compiler warning. Program worked flawlessy even before(and still does) but with less annoyance for me.. =|:-) The declaration thingywas found on the 'net by googling for "strcasetmp introduction". Made argumentsto B<--align> and B<--style> case insensetive.[2003-03-08, 20.27-22.58] Found and killed a bug which made X resourcesoverride the command line options.[2003-03-09, 07.04-10.15] Totally fixed the bug mentioned in previous comment.Added short options B<-l>, B<c> and B<-r> (for left, center and right aligningof the menu text), and changed B<--label> to B<--title>. Also changed the sizeof the menu window so that it will always be an even number of text lines(looks pretty ogly when only half or a third of the last item can be seen).Opening of window could be still more optimal though (the problem still ariseswhen X refuses to open the window with the requested height, e.g. when one usesframes in ratpoison).[2003-03-10, 00.53-04.07] v2.0 - Optimized menu redrawing routine for thenon-scrolling case (used to flicker quite nastily when moving down large menusfast). Also finally got around to add scrolling capacity for menus too large tofit all at once. Added the related B<--scroll-offset> option and X resource aswell.[2003-03-10, 22.10-23.00] v2.0.1 - Begun fixxing window size bug (interruptedby Buffy - The Vampire Slayer).[2003-03-11, 00.31-04.25][2003-03-12, 00.13-01.14] v2.0.2 - Fixed weird update/redraw bug thingy. Usedto get a totally black empty menu sometimes. Bug probably introduced in lastsession, and now deceased.[2003-03-19, 10.48-12.38][2003-03-19, 14.17-20.18] v2.1 - Wrote better handling of X resources, comletewith readable B<--debug> output and all.[2003-03-19, 22.40-09.05] v2.2 - Implemented mouse support. Made B<--item=0>special. Bugfix: Now opens a window of the correct size by telling X that itwants a window whose vertical size should be any number of pixels evenlydividable by the font height. (B<Rxvt> served as a source of inspiration forthis feature.) Also added keys Home/PgUp for going to the first item in themenu and End/PgDn to go to the last. B<--class> now really works.[2003-03-24, 15.41-16.32] v2.2.1 - Added options B<--mouse> and B<--no-mouse>and the corresponding X resource B<ratmen.mouse> -- mouse is enabled bydefault. Removed the short options B<-u> and B<-U> (synonyms forB<--unfocus-exit> and B<--no-unfocus-exit>) as to not encourage theuseage of those options on the command line (should be set using X resources).[2003-03-28, 11.29-11.32] v2.2.2 - Killed bug which didn't allow spaces to beused in font names (this little bugfix also makes it quite necessary not to endyour X resources in any extraneous spaces).=head1 BUGS=over 4=item oThe B<snazzy> mode. I'm probably going to remove it sometime in the future.B<I> don't use it and I don't see any reason why anybody else should want toeither... (Especially not now that we got scrollable B<dreary> mode..)=item oGenerates a `BadWindow' when dying of unfocus under the B<--unfocus-exit>option.This should be curable by means of XSetErrorHandler(), at least if it is notB<ratpoison> causing these things.=item oMemory leaks? There are probably several (me being totally new to C) but sincethe execution time should never really accumulate I haven't made it a priorityto kill 'em. Please tell me if you find any.=item oWhen using B<--item=0> and B<--style=snazzy> one must press arrow down *twice*to select the first item on the menu. (I'll probably fix this by removing the`snazzy' mode.)=back=head1 TODOThese are the things a want to do next in approximate order of priority: o configurable keys for up/down choose etc. o manually defined key shortcuts o automatically generated key shortcuts o multiple selection mode (space=select; enter=finalize) (introduce colors for marked entry and marked&selected entry) o incremental search in menu (using bottom item space for input) o replacing top/bottom item with arrow if scrollable in that direction=cut*/