/*
* 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);
}
syntax highlighted by Code2HTML, v. 0.9.1