/*
 * 9menu.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.com
 * 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
 *
 * Code added for -file and -path optioins.
 * Peter Seebach
 * seebs@plethora.net
 * October, 2001
 *
 * Code added to allow up and down arrow keys to go up
 * and down menu and RETURN to select an item.
 * Matthias Bauer
 * bauerm@immd1.informatik.uni-erlangen.de
 * June 2003
 *
 * Changed a couple of minor details, renamed it to larsmenu
 * so as to not cause conflicts when using package managers
 * to install both larswm and 9menu, and changed function
 * prototypes to ANSI C model. Added code to prevent it from
 * being started more than once.
 * Lars Bernhardsson
 * lab@fnurt.net
 * May 2004.
 */

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include <X11/Xutil.h>
#include <X11/keysymdef.h>
#include <X11/keysym.h>
#include <X11/Xmu/WinUtil.h>


Display *dpy;			/* lovely X stuff */
int screen;
Window root;
Window menuwin;
GC gc;
unsigned long black;
unsigned long white;
char *fgcname = NULL;
char *bgcname = NULL;
Colormap defcmap;
XColor color;
XFontStruct *font;
Atom wm_protocols;
Atom wm_delete_window;
int g_argc;			/* for XSetWMProperties to use */
char **g_argv;
int f_argc;			/* for labels read from files */
char **f_argv;
char *geometry = "";
int savex, savey;
Window savewindow;

char *fontlist[] = {		/* default font list if no -font */
  "fixed",
  NULL
};

/* the 9menu icon, for garish window managers */
#define nine_menu_width 40
#define nine_menu_height 40
static unsigned char nine_menu_bits[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
  0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00,
  0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04,
  0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff,
  0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
  0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
  0x80, 0xe0, 0x01, 0x04, 0x00, 0x80, 0xe0, 0x00, 0xfc, 0xff, 0xff, 0xe0,
  0x01, 0xfc, 0xff, 0xff, 0x20, 0x03, 0x04, 0x00, 0x80, 0x00, 0x06, 0x04,
  0x00, 0x80, 0x00, 0x0c, 0xfc, 0xff, 0xff, 0x00, 0x08, 0xfc, 0xff, 0xff,
  0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
  0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00,
  0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00,
  0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04,
  0x00, 0x80, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x00, 0x00, 0xfc, 0xff, 0xff,
  0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00,
  0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/* Modify this to your liking */
#define CONFIG_MENU_UP_KEY  XK_Up
#define CONFIG_MENU_DOWN_KEY    XK_Down
#define CONFIG_MENU_SELECT_KEY  XK_Return
#define CONFIG_MENU_CANCEL_KEY  XK_Escape

#define PADX 100
#define PADY 30

char *progname;			/* my name */
char *displayname;		/* X display */
char *fontname;			/* font */
char *labelname;		/* window and icon name */
char *filename;			/* file to read options or labels from */
int popup;			/* true if we're a popup window */
int popdown;			/* autohide after running a command */
int iconic;			/* start iconified */
int teleport;			/* teleport the menu */
int warp;			/* warp the mouse */

char **labels;			/* list of labels and commands */
char **commands;
int numitems;

char *shell = "/bin/sh";	/* default shell */

extern void usage (), run_menu (), spawn (), ask_wm_for_delete ();
extern void reap (), set_wm_hints ();
extern void redraw (), teleportmenu (), warpmouse (), restoremouse ();
extern void memory ();
extern int args ();

/* scanwins --- check if a larsmenu window is already open.
 *              Yes, this way is ugly. I know. Really, I know. =)
 */

int
larsmenu_open (void)
{
  unsigned int i, nwins, do_exit = 0;
  Window dw1, dw2, *wins;

  XQueryTree (dpy, root, &dw1, &dw2, &wins, &nwins);

  for (i = 0; i < nwins; i++)
    {
      Window client;
      XClassHint clh;

      if ((client = XmuClientWindow (dpy, wins[i])) != None)
	{
	  if (XGetClassHint (dpy, client, &clh))
	    {
	      if (clh.res_class && clh.res_name)
		{
		  if (!strcmp (clh.res_class, "larsmenu") &&
		      !strcmp (clh.res_name, "larsmenu"))
		    do_exit = 1;
		}

	      if (clh.res_class)
		XFree (clh.res_class);

	      if (clh.res_name)
		XFree (clh.res_name);
	    }
	}

      if (do_exit)
	break;
    }

  XFree ((void *) wins);

  return do_exit;
}

/* memory --- print the out of memory message and die */

void
memory (char *s)
{
  fprintf (stderr, "%s: couldn't allocate memory for %s\n", progname, s);
  exit (1);
}

/* args --- go through the argument list, set options */

int
args (int argc, char **argv)
{
  int i;

  if (argc == 0 || argv == NULL || argv[0] == '\0')
    return -1;
  for (i = 0; i < argc && argv[i] != NULL; i++)
    {
      if (strcmp (argv[i], "-display") == 0)
	{
	  displayname = argv[i + 1];
	  i++;
	}
      else if (strcmp (argv[i], "-file") == 0)
	{
	  filename = argv[i + 1];
	  i++;
	}
      else if (strcmp (argv[i], "-font") == 0)
	{
	  fontname = argv[i + 1];
	  i++;
	}
      else if (strcmp (argv[i], "-geometry") == 0)
	{
	  geometry = 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], "-popup") == 0)
	popup++;
      else if (strcmp (argv[i], "-popdown") == 0)
	popdown++;
      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], "-iconic") == 0)
	iconic++;
      else if (strcmp (argv[i], "-path") == 0)
	{
	  char pathbuf[MAXPATHLEN];
	  char *s, *t;

	  s = getenv ("PATH");
	  if (s != NULL)
	    {
	      /* append current dir to PATH */
	      getcwd (pathbuf, MAXPATHLEN);
	      t = malloc (strlen (s) + strlen (pathbuf) + 7);
	      sprintf (t, "PATH=%s:%s", pathbuf, s);
	      putenv (t);
	    }
	}
      else if (strcmp (argv[i], "-teleport") == 0)
	teleport++;
      else if (strcmp (argv[i], "-warp") == 0)
	warp++;
      else if (argv[i][0] == '-')
	usage ();
      else
	break;
    }
  return i;
}

/* main --- crack arguments, set up X stuff, run the main menu loop */

int
main (int argc, char **argv)
{
  int i, j;
  char *cp;
  XGCValues gv;
  unsigned long mask;
  int nlabels = 0;

  g_argc = argc;
  g_argv = argv;

  /* set default label name */
  if ((cp = strrchr (argv[0], '/')) == NULL)
    labelname = argv[0];
  else
    labelname = ++cp;

  ++argv;
  --argc;

  /* and program name for diagnostics */
  progname = labelname;

  i = args (argc, argv);

  numitems = argc - i;

  if (numitems <= 0 && filename == NULL)
    usage ();

  if (filename)
    {
      /* Read options and labels from file */
      char fbuf[1024];
      FILE *fp;

      fp = fopen (filename, "r");
      if (fp == NULL)
	{
	  fprintf (stderr, "%s: couldn't open '%s'\n", progname, filename);
	  exit (1);
	}
      while (fgets (fbuf, sizeof fbuf, fp))
	{
	  char *s = fbuf;
	  strtok (s, "\n");
	  if (s[0] == '-')
	    {
	      char *temp[3];
	      temp[0] = s;
	      temp[1] = strchr (s, ' ');
	      if (temp[1])
		{
		  *(temp[1]++) = '\0';
		  s = malloc (strlen (temp[1]) + 1);
		  if (s == NULL)
		    memory ("temporary argument");
		  strcpy (s, temp[1]);
		  temp[1] = s;
		}
	      temp[2] = 0;
	      args (temp[1] ? 2 : 1, temp);
	      continue;
	    }
	  if (s[0] == '#')
	    continue;
	  /* allow - in menu items to be escaped */
	  if (s[0] == '\\')
	    ++s;
	  /* allocate space */
	  if (f_argc < nlabels + 1)
	    {
	      int k;
	      char **temp = malloc (sizeof (char *) * (f_argc + 5));
	      if (temp == 0)
		memory ("temporary item");

	      for (k = 0; k < nlabels; k++)
		temp[k] = f_argv[k];

	      free (f_argv);
	      f_argv = temp;
	      f_argc += 5;
	    }
	  f_argv[nlabels] = malloc (strlen (s) + 1);
	  if (f_argv[nlabels] == NULL)
	    memory ("temporary text");
	  strcpy (f_argv[nlabels], s);
	  ++nlabels;
	}
    }

  labels = (char **) malloc ((numitems + nlabels) * sizeof (char *));
  commands = (char **) malloc ((numitems + nlabels) * sizeof (char *));
  if (commands == NULL || labels == NULL)
    memory ("command and label arrays");

  for (j = 0; j < numitems; j++)
    {
      labels[j] = argv[i + j];
      if ((cp = strchr (labels[j], ':')) != NULL)
	{
	  *cp++ = '\0';
	  commands[j] = cp;
	}
      else
	commands[j] = labels[j];
    }

  /*
   * Now we no longer need i (our offset into argv) so we recycle it,
   * while keeping the old value of j!
   */
  for (i = 0; i < nlabels; i++)
    {
      labels[j] = f_argv[i];
      if ((cp = strchr (labels[j], ':')) != NULL)
	{
	  *cp++ = '\0';
	  commands[j] = cp;
	}
      else
	commands[j] = labels[j];
      ++j;
    }

  /* And now we merge the totals */
  numitems += nlabels;

  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);
  /*
   * This used to be
   * black = BlackPixel(dpy, screen);
   * white = WhitePixel(dpy, screen);
   */
  defcmap = DefaultColormap (dpy, screen);
  if (fgcname == NULL
      || XParseColor (dpy, defcmap, fgcname, &color) == 0
      || XAllocColor (dpy, defcmap, &color) == 0)
    black = BlackPixel (dpy, screen);
  else
    black = color.pixel;

  if (bgcname == NULL
      || XParseColor (dpy, defcmap, bgcname, &color) == 0
      || XAllocColor (dpy, defcmap, &color) == 0)
    white = WhitePixel (dpy, screen);
  else
    white = color.pixel;

  /* try user's font first */
  if (fontname != NULL)
    {
      font = XLoadQueryFont (dpy, fontname);
      if (font == NULL)
	fprintf (stderr, "%s: warning: can't load font %s\n",
		 progname, fontname);
    }

  /* if no user font, try one of our default fonts */
  if (font == NULL)
    {
      for (i = 0; fontlist[i] != NULL; i++)
	{
	  font = XLoadQueryFont (dpy, fontlist[i]);
	  if (font != NULL)
	    break;
	}
    }

  if (font == NULL)
    {
      fprintf (stderr, "%s: fatal: cannot load a font\n", progname);
      exit (1);
    }

  gv.foreground = black ^ white;
  gv.background = white;
  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);

  if (!larsmenu_open ())
    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;
    }

  if (strncmp (com, "exec ", 5) != 0)
    {
      pid = fork ();
      if (pid < 0)
	{
	  fprintf (stderr, "%s: can't fork\n", progname);
	  return;
	}
      else if (pid > 0)
	return;
    }
  else
    {
      com += 5;
    }

  close (ConnectionNumber (dpy));
  execl (shell, sh_base, "-c", com, NULL);
  execl ("/bin/sh", "sh", "-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] [-font fname] ",
	   progname);
  fprintf (stderr, "[-file filename] [-path]");
  fprintf (stderr, "[-geometry geom] [-shell shell]  [-label name] ");
  fprintf (stderr, "[-popup] [-popdown] [-iconic]  [-teleport] ");
  fprintf (stderr, "[-warp]  menitem:command ...\n");
  exit (0);
}

/* run_menu --- put up the window, execute selected commands */

void
run_menu (void)
{
  XEvent ev;
  XClientMessageEvent *cmsg;
  KeySym key;
  int i, cur, old, wide, high, ico, dx, dy;

  dx = 0;
  for (i = 0; i < numitems; i++)
    {
      wide = XTextWidth (font, labels[i], strlen (labels[i])) + PADX;
      if (wide > dx)
	dx = wide;
    }
  wide = dx;

  old = cur = -1;

  high = font->ascent + font->descent + PADY;
  dy = numitems * high;

  set_wm_hints (wide, dy);

  ask_wm_for_delete ();

#define	MenuMask (ButtonPressMask|ButtonReleaseMask\
	|LeaveWindowMask|PointerMotionMask|ButtonMotionMask\
	|ExposureMask|StructureNotifyMask|KeyPressMask)

  XSelectInput (dpy, menuwin, MenuMask);

  XMapWindow (dpy, menuwin);

  ico = 1;			/* warp to first item */
  i = 0;			/* save menu Item position */

  for (;;)
    {
      XNextEvent (dpy, &ev);
      switch (ev.type)
	{
	default:
	  fprintf (stderr, "%s: unknown ev.type %d\n", progname, ev.type);
	  break;
	case ButtonRelease:
	  /* ignore mouse wheel */
	  if (ev.xbutton.button == Button4 || ev.xbutton.button == Button5)
	    break;
	  i = ev.xbutton.y / high;
	  if (ev.xbutton.x < 0 || ev.xbutton.x > wide)
	    break;
	  else if (i < 0 || i >= numitems)
	    break;
	  if (warp)
	    restoremouse ();
	  if (strcmp (labels[i], "exit") == 0)
	    {
	      if (commands[i] != labels[i])
		{
		  spawn (commands[i]);
		}
	      return;
	    }
	  spawn (commands[i]);
	  if (popup)
	    return;
	  if (cur >= 0 && cur < numitems)
	    XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
	  if (popdown)
	    XIconifyWindow (dpy, menuwin, screen);
	  cur = -1;
	  break;
	case ButtonPress:
	case MotionNotify:
	  old = cur;
	  cur = ev.xbutton.y / high;
	  if (ev.xbutton.x < 0 || ev.xbutton.x > wide)
	    cur = -1;
	  else if (cur < 0 || cur >= numitems)
	    cur = -1;
	  if (cur == old)
	    break;
	  if (old >= 0 && old < numitems)
	    XFillRectangle (dpy, menuwin, gc, 0, old * high, wide, high);
	  if (cur >= 0 && cur < numitems)
	    XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
	  break;
	case KeyPress:
	  key = XKeycodeToKeysym (dpy, ev.xkey.keycode, 0);
	  if (key != CONFIG_MENU_UP_KEY && key != CONFIG_MENU_CANCEL_KEY &&
	      key != CONFIG_MENU_DOWN_KEY && key != CONFIG_MENU_SELECT_KEY)
	    break;

	  if (key == CONFIG_MENU_CANCEL_KEY)
	    return;

	  if (key == CONFIG_MENU_UP_KEY)
	    {
	      old = cur;
	      cur--;
	    }
	  else if (key == CONFIG_MENU_DOWN_KEY)
	    {
	      old = cur;
	      cur++;
	    }

	  while (cur < 0)
	    cur += numitems;

	  cur %= numitems;

	  if (key == CONFIG_MENU_UP_KEY || key == CONFIG_MENU_DOWN_KEY)
	    {
	      if (cur == old)
		break;
	      if (old >= 0 && old < numitems && cur != -1)
		XFillRectangle (dpy, menuwin, gc, 0, old * high, wide, high);
	      if (cur >= 0 && cur < numitems && cur != -1)
		XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
	      break;
	    }

	  if (warp)
	    restoremouse ();
	  if (key == CONFIG_MENU_SELECT_KEY)
	    {
	      if (strcmp (labels[cur], "exit") == 0)
		{
		  if (commands[cur] != labels[cur])
		    {
		      spawn (commands[cur]);
		    }
		  return;
		}
	      spawn (commands[cur]);
	    }

	  if (popup)
	    return;
	  if (popdown)
	    XIconifyWindow (dpy, menuwin, screen);
	  break;
	case LeaveNotify:
	  cur = old = -1;
	  XClearWindow (dpy, menuwin);
	  redraw (cur, high, wide);
	  break;
	case ReparentNotify:
	case ConfigureNotify:
	  /*
	   * ignore these, they come from XMoveWindow
	   * and are enabled by Struct..
	   */
	  break;
	case UnmapNotify:
	  ico = 1;
	  XClearWindow (dpy, menuwin);
	  break;
	case MapNotify:
	  if (ico)
	    {
	      if (teleport)
		teleportmenu (i, wide, high);
	      else if (warp)
		warpmouse (i, wide, high);
	    }
	  XClearWindow (dpy, menuwin);
	  redraw (cur = i, high, wide);
	  ico = 0;
	  break;
	case Expose:
	  XClearWindow (dpy, menuwin);
	  redraw (cur, high, wide);
	  break;
	case ClientMessage:
	  cmsg = &ev.xclient;
	  if (cmsg->message_type == wm_protocols
	      && cmsg->data.l[0] == wm_delete_window)
	    return;
	case MappingNotify:	/* why do we get this? */
	  break;
	}
    }
}

/* set_wm_hints --- set all the window manager hints */

void
set_wm_hints (int wide, int high)
{
  Pixmap iconpixmap;
  XWMHints *wmhints;
  XSizeHints *sizehints;
  XClassHint *classhints;
  XTextProperty wname, iname;

  if ((sizehints = XAllocSizeHints ()) == NULL)
    memory ("size hints");

  if ((wmhints = XAllocWMHints ()) == NULL)
    memory ("window manager hints");

  if ((classhints = XAllocClassHint ()) == NULL)
    memory ("class hints");

  /* 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, geometry, "", 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)
    memory ("window name structure");

  if (XStringListToTextProperty (&labelname, 1, &iname) == 0)
    memory ("icon name structure");

  menuwin = XCreateSimpleWindow (dpy, root, sizehints->x, sizehints->y,
				 sizehints->width, sizehints->height, 1,
				 black, white);

  iconpixmap = XCreateBitmapFromData (dpy, menuwin,
				      (char *) nine_menu_bits,
				      nine_menu_width, nine_menu_height);

  wmhints->icon_pixmap = iconpixmap;
  wmhints->input = False;	/* no keyboard input */
  if (iconic)
    wmhints->initial_state = IconicState;
  else
    wmhints->initial_state = NormalState;

  wmhints->flags = IconPixmapHint | StateHint | InputHint;

  classhints->res_name = progname;
  classhints->res_class = "larsmenu";

#ifdef SET_PROPERTIES_MANUALLY
  /*
   * For some reason, XSetWMProperties (see below) is failing
   * John O'Donnell replaces it with the following commands
   * (this leaves out XSetWMClientMachine,
   * and also environment variable checking from ClassHint)
   */
  XSetWMName (dpy, menuwin, &wname);
  XSetWMIconName (dpy, menuwin, &iname);
  XSetCommand (dpy, menuwin, g_argv, g_argc);
  XSetWMHints (dpy, menuwin, wmhints);
  XSetClassHint (dpy, menuwin, classhints);
  XSetWMNormalHints (dpy, menuwin, sizehints);
#else
  XSetWMProperties (dpy, menuwin, &wname, &iname,
		    g_argv, g_argc, sizehints, wmhints, classhints);
#endif
}

/* 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 redraw the menu */

void
redraw (int cur, int high, int wide)
{
  int tx, ty, i;

  for (i = 0; i < numitems; i++)
    {
      tx = (wide - XTextWidth (font, labels[i], strlen (labels[i]))) / 2;
      ty = i * high + font->ascent + (PADY / 2);
      XDrawString (dpy, menuwin, gc, tx, ty, labels[i], strlen (labels[i]));
    }
  if (cur >= 0 && cur < numitems)
    XFillRectangle (dpy, menuwin, gc, 0, cur * high, wide, high);
}

/* teleportmenu --- move the menu to the right place */

void
teleportmenu (int cur, int wide, int high)
{
  int x, y, dummy;
  Window wdummy;

  if (XQueryPointer (dpy, menuwin, &wdummy, &wdummy, &x, &y,
		     &dummy, &dummy, (unsigned int *) &dummy))
    XMoveWindow (dpy, menuwin, x - wide / 2, y - cur * high - high / 2);
}

/* warpmouse --- bring the mouse to the menu */

void
warpmouse (int cur, int wide, int high)
{
  int dummy;
  Window wdummy;
  int offset;

  /* move tip of pointer into middle of menu item */
  offset = (font->ascent + font->descent + 1) / 2;
  offset += 6;			/* fudge factor */

  if (XQueryPointer (dpy, menuwin, &wdummy, &wdummy, &savex, &savey,
		     &dummy, &dummy, (unsigned int *) &dummy))
    XWarpPointer (dpy, None, menuwin, 0, 0, 0, 0,
		  wide / 2, cur * high - high / 2 + offset);
}

/* restoremouse --- put the mouse back where it was */

void
restoremouse (void)
{
  XWarpPointer (dpy, menuwin, root, 0, 0, 0, 0, savex, savey);
}


syntax highlighted by Code2HTML, v. 0.9.1