/* * ratmenu.c * * 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 -fg and -bg 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 * djw@reactor-core.org * September, 2001 */ #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> char version[] = "@(#) ratmenu version 1.4"; #define FONT "9x15bold" #define MenuMask (ExposureMask|StructureNotifyMask|KeyPressMask) Display *dpy; /* lovely X stuff */ int screen; Window root; Window menuwin; GC gc; unsigned long fg; unsigned long bg; char *fgcname = NULL; char *bgcname = NULL; 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; char *progname; /* my name */ char *displayname; /* X display */ char *fontname = FONT; /* font */ char *labelname; /* window and icon name */ char **labels; /* list of labels and commands */ char **commands; int numitems; enum {left, center, right} align; enum {snazzy, dreary} style; char *shell = "/bin/sh"; /* default shell */ void ask_wm_for_delete(void); void reap(int); void (*redraw) (int, int, int); void redraw_snazzy(int, int, int); void redraw_dreary(int, int, int); void run_menu(void); void set_wm_hints(int, int); void spawn(char*); void usage(void); /* 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; g_argv = argv; /* set default label name */ if ((cp = strrchr(argv[0], '/')) == NULL) labelname = argv[0]; else labelname = ++cp; /* and program name for diagnostics */ progname = labelname; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-display") == 0) { displayname = argv[i+1]; i++; } else if (strcmp(argv[i], "-font") == 0) { fontname = argv[i+1]; i++; } else if (strcmp(argv[i], "-label") == 0) { labelname = argv[i+1]; i++; } else if (strcmp(argv[i], "-shell") == 0) { shell = argv[i+1]; i++; } else if (strcmp(argv[i], "-fg") == 0) fgcname = argv[++i]; else if (strcmp(argv[i], "-bg") == 0) bgcname = argv[++i]; else if (strcmp(argv[i], "-align") == 0) { if (strcmp(argv[i+1], "left") == 0) align = left; else if (strcmp(argv[i+1], "center") == 0) align = center; else if (strcmp(argv[i+1], "right") == 0) align = right; else usage(); i++; } else if (strcmp(argv[i], "-style") == 0) { if (strcmp(argv[i+1], "snazzy") == 0) style = snazzy; else if (strcmp(argv[i+1], "dreary") == 0) style = dreary; else usage(); i++; } else if (strcmp(argv[i], "-version") == 0) { printf("%s\n", version); exit(0); } else if (argv[i][0] == '-') usage(); else break; } if (style == snazzy) redraw = redraw_snazzy; else /* style == dreary */ redraw = redraw_dreary; numitems = argc - i; if (numitems <= 0) usage(); labels = &argv[i]; commands = (char **) malloc(numitems * sizeof(char *)); if (commands == NULL) { fprintf(stderr, "%s: no memory!\n", progname); exit(1); } for (i = 0; i < numitems; i++) { if ((cp = strchr(labels[i], ':')) != NULL) { *cp++ = '\0'; commands[i] = cp; } else commands[i] = labels[i]; } 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); } screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); dcmap = DefaultColormap (dpy, screen); if (fgcname == NULL || XParseColor(dpy, dcmap, fgcname, &color) == 0 || XAllocColor(dpy, dcmap, &color) == 0) fg = BlackPixel(dpy, screen); else fg = color.pixel; if (bgcname == NULL || XParseColor(dpy, dcmap, bgcname, &color) == 0 || XAllocColor(dpy, dcmap, &color) == 0) bg = WhitePixel(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(); 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); } /* usage --- print a usage message and die */ void usage(void) { fprintf(stderr, "usage: %s [-display displayname] [-style {snazzy|dreary} " "[-shell shell] [-label name] [-align {left|center|right}] " "[-fg fgcolor] [-bg bgcolor] [-font fname] " "[-version] menitem:command ...\n", progname); exit(0); } /* run_menu --- put up the window, execute selected commands */ void run_menu(void) { KeySym key; XEvent ev; XClientMessageEvent *cmsg; int i, cur, wide, high, dx, dy; 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; set_wm_hints(wide, dy); ask_wm_for_delete(); XSelectInput(dpy, menuwin, MenuMask); XMapWindow(dpy, menuwin); cur = 0; /* Currently selected menu item */ for (;;) { XNextEvent(dpy, &ev); switch (ev.type) { case KeyPress: XLookupString(&ev.xkey, NULL, 0, &key, NULL); switch (key) { case XK_Escape: case XK_q: return; break; case XK_Right: case XK_Return: case XK_l: spawn(commands[cur]); return; break; case XK_Tab: case XK_space: case XK_Down: case XK_d: case XK_j: ++cur == numitems ? cur = 0 : 0 ; redraw(cur, high, wide); break; case XK_BackSpace: case XK_Up: case XK_u: case XK_k: cur-- == 0 ? cur = numitems - 1 : 0 ; redraw(cur, high, wide); break; } break; case UnmapNotify: XClearWindow(dpy, menuwin); break; case MapNotify: redraw(cur, high, wide); break; case Expose: redraw(cur, high, wide); break; case ClientMessage: cmsg = &ev.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) { XWMHints *wmhints; XSizeHints *sizehints; XClassHint *classhints; XTextProperty wname; if ((sizehints = XAllocSizeHints()) == NULL) { fprintf(stderr, "%s: could not allocate size hints\n", progname); exit(1); } if ((wmhints = XAllocWMHints()) == NULL) { fprintf(stderr, "%s: could not allocate window manager hints\n", progname); exit(1); } if ((classhints = XAllocClassHint()) == NULL) { fprintf(stderr, "%s: could not allocate class hints\n", progname); exit(1); } /* fill in hints in order to parse geometry spec */ sizehints->width = sizehints->min_width = sizehints->max_width = wide; sizehints->height = sizehints->min_height = sizehints->max_height = high; sizehints->flags = USSize|PSize|PMinSize|PMaxSize; if (XWMGeometry(dpy, screen, "", "", 1, sizehints, &sizehints->x, &sizehints->y, &sizehints->width, &sizehints->height, &sizehints->win_gravity) & (XValue|YValue)) sizehints->flags |= USPosition; /* override -geometry for size of window */ sizehints->width = sizehints->min_width = sizehints->max_width = wide; sizehints->height = sizehints->min_height = sizehints->max_height = high; if (XStringListToTextProperty(& labelname, 1, & wname) == 0) { fprintf(stderr, "%s: could not allocate window name structure\n", progname); exit(1); } 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 = "9menu"; 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: could not ask for clean delete\n", progname); } /* redraw --- actually draw the menu */ void redraw_snazzy (int cur, int high, int wide) { int i, j, ty, tx; XClearWindow(dpy, menuwin); for (i = 0, j = cur; i < numitems; i++, j++) { j %= numitems; if (align == left) { tx = 0; } else if (align == center) { tx = (wide - XTextWidth(font, labels[j], strlen(labels[j]))) / 2; } else {/* align == right */ tx = wide - XTextWidth(font, labels[j], strlen(labels[j])); } 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_dreary (int cur, int high, int wide) { int i, ty, tx; XClearWindow(dpy, menuwin); for (i = 0; i < numitems; i++) { if (align == left) { tx = 0; } else if (align == center) { tx = (wide - XTextWidth(font, labels[i], strlen(labels[i]))) / 2; } else {/* align == right */ tx = wide - XTextWidth(font, labels[i], strlen(labels[i])); } 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); }