/*
 * 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