/*  Part of XPCE --- The SWI-Prolog GUI toolkit

    Author:        Jan Wielemaker and Anjo Anjewierden
    E-mail:        jan@swi.psy.uva.nl
    WWW:           http://www.swi.psy.uva.nl/projects/xpce/
    Copyright (c)  1985-2002, University of Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

#include <h/kernel.h>
#include <h/graphics.h>
#include <math.h>
#include "include.h"
#include <X11/keysym.h>
#include <X11/Xproto.h>			/* get request codes */
#include <locale.h>

#undef roundup
#define roundup(v, n)		((((v)+(n)-1)/(n))*(n))
#define rescale(v, o, n)	((v) * (n) / (o))
#define XBRIGHT ((1L<<16)-1)
#define INTENSITY(r, g, b) ((r*20 + g*32 + b*18)/(20+32+18))

XtAppContext	ThePceXtAppContext;	/* X toolkit application context */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The Mac keyboard has a bit different   viewpoint. Alt is primairily used
for creating non-ascii characters, while the command key is used to give
commands. The command key is by default passed as `Mod2'.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef __APPLE__
#define DefMetaMask Mod2Mask
#else
#define DefMetaMask Mod1Mask
#endif

static unsigned int MetaMask = DefMetaMask;	/* Key-mask for meta */


		 /*******************************
		 *       X11 APP CONTEXT	*
		 *******************************/

static int
x_error_handler(Display *display, XErrorEvent *error)
{ if ( !catchedErrorPce(PCE, NAME_xError) )
  { char msg[1024];
    char request[100];
    char buf[100];

					/* XSetInputFocus() can generate a */
					/* BadMatch that is hard to avoid */
    if ( error->request_code == X_SetInputFocus &&
	 error->error_code == BadMatch )
      return 0;

    XGetErrorText(display, error->error_code, msg, 1024);
    sprintf(buf, "%d", error->request_code);
    XGetErrorDatabaseText(display, "XRequest", buf,
			  "Unknown request", request, 100);
    Cprintf("X error of failed request: %s\n", msg);
    Cprintf("Major opcode of failed request: %d (%s)\n",
	    error->request_code, request);
    Cprintf("Minor opcode of failed request: %d\n", error->minor_code);
    Cprintf("Resource id in failed request:  0x%x\n",
	    (unsigned int) error->resourceid);
    Cprintf("Serial number of failed request: %ld\n", error->serial);

    errorPce(NIL, NAME_xError);
  }

  return 0;				/* what to return here? */
}


static void
xt_warning_handler(char *message)
{ if ( strcmp(message, "Select failed; error code 9") != 0 )
    Cprintf("XPCE Xt warning: %s\n", message);
}


#define USE_XDEFAULT_APP_CONTEXT 1

#ifdef USE_XDEFAULT_APP_CONTEXT
extern XtAppContext _XtDefaultAppContext(void);
#undef XtCreateApplicationContext
#define XtCreateApplicationContext _XtDefaultAppContext
#endif

static int use_x_init_threads = TRUE;

void *
pceXtAppContext(void * ctx)
{ if ( ThePceXtAppContext == NULL )
  { if ( ctx != NULL )
    { ThePceXtAppContext = ctx;
      XSetErrorHandler(x_error_handler);
    } else
    {
#if defined(_REENTRANT) && defined(HAVE_XINITTHREADS)
      if ( XPCE_mt == TRUE )
      { if ( use_x_init_threads )
	  XInitThreads();
      } else
      { XPCE_mt = -1;
      }
#else
	XPCE_mt = -1;
#endif

      XtToolkitInitialize();
      XSetErrorHandler(x_error_handler);

      if ( (ThePceXtAppContext = XtCreateApplicationContext()) == NULL )
      { errorPce(TheDisplayManager(), NAME_noApplicationContext);
	fail;
      }
      XtAppSetWarningHandler(ThePceXtAppContext, xt_warning_handler);

      if ( !XtSetLanguageProc(ThePceXtAppContext, NULL, NULL) )
      { errorPce(TheDisplayManager(), NAME_noLocaleSupport,
		 CtoName(setlocale(LC_ALL, NULL)));
	fail;
      }
    }
  }

  return ThePceXtAppContext;
}


status
X11ThreadsDisplay(DisplayObj d, BoolObj val)
{ if ( ThePceXtAppContext )
    return errorPce(d, NAME_x11Threads);

  use_x_init_threads = (val == ON ? TRUE : FALSE);

  succeed;
}


		 /*******************************
		 *	 WIDGET REFERENCE	*
		 *******************************/

Widget
widgetWindow(PceWindow sw)
{ return (Widget) sw->ws_ref;
}


Widget
widgetFrame(FrameObj fr)
{ return fr->ws_ref ? ((frame_ws_ref *)fr->ws_ref)->widget : NULL;
}

void
setXImageImage(Image image, XImage *i)
{ image->ws_ref = i;
}


		 /*******************************
		 *	     X11 ATOMS		*
		 *******************************/

#if O_MOTIF
#include <Xm/Xm.h>
#include <Xm/AtomMgr.h>			/* was X11? */
#include <Xm/Protocols.h>		/* idem */

#define XInternAtom(d, nm, v) XmInternAtom(d, nm, v)
#define XGetAtomName(d, atom) XmAtomToName(d, atom)
#endif

Atom
DisplayAtom(DisplayObj d, Name name)
{ DisplayWsXref r = d->ws_ref;

  return XInternAtom(r->display_xref, strName(name), False);
}


char *
DisplayAtomToString(DisplayObj d, Atom atom)
{ DisplayWsXref r = d->ws_ref;

  return XGetAtomName(r->display_xref, atom);
}


Atom
FrameAtom(FrameObj fr, Name name)
{ return DisplayAtom(fr->display, name);
}


char *
FrameAtomToString(FrameObj fr, Atom a)
{ return DisplayAtomToString(fr->display, a);
}


Atom
WmProtocols(FrameObj fr)
{ return FrameAtom(fr, CtoName("WM_PROTOCOLS"));
}


		 /*******************************
		 *	     POSTSCRIPT		*
		 *******************************/

typedef struct
{ int bits;				/* bit remaining */
  int depth;				/* postscript depth */
  int val;				/* current value */
  int count;				/* # emited bytes */
} ps_stat;


static void
put_value(ps_stat *s, int val)
{ static char print[] = { '0', '1', '2', '3', '4', '5', '6', '7',
			  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  s->bits -= s->depth;
  s->val |= val << s->bits;

  if ( s->bits == 0 )
  { ps_put_char(print[(s->val >> 4) & 0xf]);
    ps_put_char(print[s->val & 0xf]);
    if ( (++s->count % 32) == 0 )
      ps_put_char('\n');
    s->bits = 8; s->val = 0;
  }
}


int
shift_for_mask(unsigned long mask)
{ unsigned long m = 0x1;
  int shift = 0;

  assert(mask);
  while((mask&m) == 0)
  { m <<= 1;
    shift++;
  }

  return shift;
}


status
postscriptXImage(XImage *im, XImage *mask,
		 int fx, int fy, int w, int h,
		 Display *disp,
		 Colormap cmap,
		 int depth,
		 int iscolor)
{ int x, y, w8;
  unsigned char psmap[256];
  int psbright;
  int direct = FALSE;
  ps_stat stat;

  if ( depth == 0 )			/* PostScript depth is 1, 2, 4, or 8 */
  { depth = im->depth;

    if ( depth == 3 )
      depth = 2;
    else if ( depth > 4 && depth < 8 )
      depth = 4;
    else if ( depth > 8 )
      depth = 8;
  }

  psbright = (1<<depth) - 1;

  if ( im->format == XYBitmap )		/* binary bitmap */
  { psmap[0] = 1;
    psmap[1] = 0;
  } else if ( im->depth <= 8 )		/* colour-mapped */
  { int entries	= 1<<im->depth;
    int i;
    XColor data[256];

    for(i=0; i<entries; i++)
      data[i].pixel = i;

    XQueryColors(disp, cmap, data, entries);

    for(i=0; i<entries; i++)
    { int val = intensityXColor(&data[i]);

      psmap[i] = rescale(val, XBRIGHT, psbright);
    }
  } else				/* direct colour */
  { direct = TRUE;
  }

  w8 = roundup(w, 8);
  stat.count = 0;
  stat.val = 0;
  stat.bits = 8;
  stat.depth = depth;

  for(y = fy; y < h; y++)
  { if ( !direct )
    { for(x = fx; x < w8; x++)
      { int pixval;

	if ( mask && XGetPixel(mask, x, y) == 0L )
	  pixval = psbright;
	else
	  pixval = (x < w ? psmap[XGetPixel(im, x, y)] : psbright);

	put_value(&stat, pixval);
      }
    } else
    { int r_shift = shift_for_mask(im->red_mask);
      int g_shift = shift_for_mask(im->green_mask);
      int b_shift = shift_for_mask(im->blue_mask);
      int r_bright = im->red_mask   >> r_shift;
      int g_bright = im->green_mask >> g_shift;
      int b_bright = im->blue_mask  >> b_shift;

      DEBUG(NAME_image, Cprintf("Line %03d", y));
      for(x = fx; x < w8; x++)
      { unsigned long pixel;
	int pixval;
	int r, g, b;

	if ( mask && XGetPixel(mask, x, y) == 0L )
	{ if ( iscolor )
	  { put_value(&stat, psbright);
	    put_value(&stat, psbright);
	    put_value(&stat, psbright);
	  } else
	  { put_value(&stat, psbright);
	  }
	} else
	{ pixel = XGetPixel(im, x, y);
	  r = (pixel & im->red_mask)   >> r_shift;
	  g = (pixel & im->green_mask) >> g_shift;
	  b = (pixel & im->blue_mask)  >> b_shift;
	  DEBUG(NAME_image, Cprintf(" %x/%x/%x", r, g, b));

	  if ( depth == 1 )
	  { if ( r+g+b > (r_bright+g_bright+b_bright)/2 )
	      pixval = 1;
	    else
	      pixval = 0;

	    put_value(&stat, pixval);
	  } else
	  { r = rescale(r, r_bright, psbright);
	    g = rescale(g, g_bright, psbright);
	    b = rescale(b, b_bright, psbright);

	    if ( iscolor )
	    { put_value(&stat, r);
	      put_value(&stat, g);
	      put_value(&stat, b);
	    } else
	    { pixval = (x < w ? INTENSITY(r, g, b) : psbright);

	      put_value(&stat, pixval);
	    }
	  }
	}
      }
      DEBUG(NAME_image, Cprintf("\n"));
    }
  }

  succeed;
}


		 /*******************************
		 *       COLOUR HANDLING	*
		 *******************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Return greyscale intensity of an XColor object.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
intensityXColor(XColor *c)
{ unsigned int r = c->red;
  unsigned int g = c->green;
  unsigned int b = c->blue;

  return INTENSITY(r, g, b);
}


unsigned long
getPixelColour(Colour c, DisplayObj d)
{ XColor *color = (XColor *) getXrefObject(c, d);

  return color ? color->pixel : 0L;
}


void
x11_set_gc_foreground(DisplayObj d, Any fg, int gcs, GC *gc)
{ XGCValues values;
  unsigned long mask;
  DisplayWsXref r = d->ws_ref;

  if ( instanceOfObject(fg, ClassColour) )
  { unsigned long pixel = getPixelColour(fg, d);

    values.foreground = pixel;
    values.fill_style = FillSolid;
    mask	      = (GCForeground|GCFillStyle);
  } else
  { Pixmap pm   = (Pixmap) getXrefObject(fg, d);

    values.tile       = pm;
    values.fill_style = FillTiled;
    mask	      = (GCTile|GCFillStyle);
  }

  for(; gcs > 0; gcs--, gc++)
    XChangeGC(r->display_xref, *gc, mask, &values);
}


static int
distanceColours(Name vt, XColor *c1, XColor *c2)
{ if ( vt == NAME_greyScale )
  { int i1 = intensityXColor(c1);
    int i2 = intensityXColor(c2);

    return abs(i1 - i2);
  } else
  { int dr = ((int)c1->red - (int)c2->red) / 4;
    int dg = ((int)c1->green - (int)c2->green) / 4;
    int db = ((int)c1->blue - (int)c2->blue) / 4;

    return (int)sqrt((double)(dr*dr + dg*dg + db*db)) * 4;
  }
}


status
allocNearestColour(Display *display, Colormap map, int depth, Name vt,
		   XColor *c)
{ XColor *colors;
  int entries = 1<<depth;

  if ( (colors = alloc(entries * sizeof(XColor))) )
  { int i, j;

    for(i=0; i<entries; i++)
      colors[i].pixel = i;

    DEBUG(NAME_colour, Cprintf("Looking for %d %d %d\n",
			       c->red, c->green, c->blue));

    if ( isDefault(vt) )		/* TBD */
    { Visual *v = XDefaultVisual(display, DefaultScreen(display));
      int vclass = v->class;

      switch(vclass)
      { case StaticGray: vt = NAME_staticGrey;
        case GrayScale:	 vt = NAME_greyScale;
      }
    }

    XQueryColors(display, map, colors, entries);

    for(j=0; j<entries; j++)
    { XColor *cb = NULL;
      int badness = 1000000;
      XColor *e = colors;

      for(i=0; i<entries; i++, e++)
      { if ( e->flags != -1 )		/* tried this one */
	{ int d = distanceColours(vt, c, e);

	  if ( d < badness )
	  { cb = e;
	    badness = d;
	  }
	}
      }

      assert(cb);

      DEBUG(NAME_colour, Cprintf("Mapped colour %d %d %d --> %d %d %d\n",
				 c->red, c->green, c->blue,
				 cb->red, cb->green, cb->blue));

      *c = *cb;
      if ( XAllocColor(display, map, c) )
      { unalloc(entries * sizeof(XColor), colors);
	succeed;
      } else
      {	cb->flags = -1;			/* don't try this one anymore! */
	DEBUG(NAME_colour, Cprintf("Can't allocate, trying another one\n"));
      }
    }
  }

  fail;
}


		/********************************
		*      X-EVENT TRANSLATION	*
		********************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This function gets the keyboard event id   from an Xevent. Function keys
are mapped to XPCE names. Normal keys   must  be mapped to their UNICODE
character.  This  is  a  bit  complicated.    According   to  the  docs,
XLookupString only returns ISO-Latin-1 characters.   In practice it also
appears to return multibyte sequences, especially   UTF-8. It is unclear
whether all X11 systems do this and whether the output is always UTF-8.

Dispite many references, XtSetLanguageProc() and  XLookupString() do not
ensure proper handling of dead keys (' +   e). Possibly this is due to a
weird combination of event-related X/Xt  functions   used  in  low level
XPCE, but even with browsing the X11 sources I could not find it. As the
long term aim was  to  move   to  XwcLookupString()  anyway, proper -but
simple- support for XIM has been implemented.

A good document is:

	http://home.catv.ne.jp/pp/ginoue/im/xim-e.html

One of the issues is the window for an IC. Can/should this always be the
frame? At the moment it is the  X11 window realising the event receiver.
Using the frame window for all XIM operations does not work.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef O_XIM
static void
adjustEventMask(Display *d, Window w, XIC ic)
{ XWindowAttributes winatts;
  long fevent;

  XGetICValues(ic, XNFilterEvents, &fevent, NULL);
  XGetWindowAttributes(d, w, &winatts);
  XSelectInput(d, w, winatts.your_event_mask|fevent);
}


static XIC
getICWindow(Any obj)
{ FrameObj fr;
  FrameWsRef wsfr;
  DisplayWsXref d;
  Widget w;

  if ( instanceOfObject(obj, ClassFrame) )
  { fr = obj;
    w = widgetFrame(fr);
    DEBUG(NAME_event, Cprintf("Associating IC with %s ...", pp(fr)));
  } else if ( instanceOfObject(obj, ClassWindow) )
  { PceWindow sw = obj;
    if ( !(fr = getFrameWindow(sw, OFF)) )
      fail;
    w = widgetWindow(sw);
    DEBUG(NAME_event, Cprintf("Associating IC with %s ...", pp(sw)));
  } else
    fail;

  if ( !w )
    fail;				/* possible on MacOS? */
  d = fr->display->ws_ref;

  if ( (wsfr = fr->ws_ref) && d->im )
  { XIC ic;
    Window xwin = XtWindow(w);

    if ( wsfr->ic )
    { if ( wsfr->icwin != xwin )
      { XSetICValues(wsfr->ic,
		     XNClientWindow, xwin,
		     NULL);
	wsfr->icwin = xwin;
	adjustEventMask(d->display_xref, xwin, wsfr->ic);
	DEBUG(NAME_event, Cprintf("Re-using IC %p (switched window)\n",
				  wsfr->ic));
      } else
      { DEBUG(NAME_event, Cprintf("Re-using IC %p\n", wsfr->ic));
      }

      return wsfr->ic;
    }

    ic = XCreateIC(d->im,
		   XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
		   XNClientWindow, xwin,
		   NULL);
    if( !ic )
    { DEBUG(NAME_event, Cprintf("Could not open X Input Context\n"));
      fail;
    }
    adjustEventMask(d->display_xref, xwin, ic);

    DEBUG(NAME_event, Cprintf("Created IC %p\n", ic));
    wsfr->ic    = ic;
    wsfr->icwin = xwin;

    return ic;
  }

  fail;
}

#endif /*O_XIM*/

static Any
keycode_to_name(Any sw, XEvent *event)
{ char buf[256];
  int count;
  KeySym sym;
#ifdef O_XIM
  XIC ic = getICWindow(sw);

  if ( ic )
  { charW wbuf[256];
    charW *winput = wbuf;
    Status status;
    int has_chars = FALSE;
    int has_sym = FALSE;

  again:
    count = XwcLookupString(ic, (XKeyPressedEvent*)event,
			    winput, sizeof(wbuf)/sizeof(wchar_t),
			    &sym, &status);
    DEBUG(NAME_event,
	  { int i;
	    Cprintf("Got %d wide characters:", count);
	    for(i=0; i<count; i++)
	      Cprintf(" %d", wbuf[i]);
		Cprintf("\n");
	  });

    switch(status)
    { case XBufferOverflow:
	winput = pceMalloc(sizeof(wchar_t)*(count+1));
        goto again;
      case XLookupNone:
	fail;
      case XLookupKeySym:
	has_sym = TRUE;
        break;
      case XLookupBoth:
	has_chars = has_sym = TRUE;
        break;
      case XLookupChars:
	has_chars = TRUE;
        break;
    }

    (void)has_chars;			/* not sure ... */

    if ( has_sym )
    { switch(sym)
      { case XK_BackSpace:
	  if ( event->xkey.state & MetaMask )
	    return toInt(8+META_OFFSET);
					/* cannot be a buffer overflow */
	return NAME_backspace;
      }
    }

    if ( count == 1 )
    { int c = wbuf[0];

      if ( event->xkey.state & MetaMask )	/* meta depressed */
	c += META_OFFSET;

      return toInt(c);
    }

    if ( !has_sym )
    { if ( winput != wbuf )		/* TBD: multi-character input */
	pceFree(winput);

      fail;
    }
  } else
#endif /*O_XIM*/
  { count = XLookupString((XKeyPressedEvent *) event,
			  buf, sizeof(buf), &sym, NULL);
    DEBUG(NAME_event,
	  { int i;
	    Cprintf("Got %d characters:", count);
	    for(i=0; i<count; i++)
	      Cprintf(" %d", buf[i]&0xff);
		Cprintf("\n");
	  });

    switch(sym)				/* special ones */
    { case XK_BackSpace:
	if ( event->xkey.state & MetaMask )
	  return toInt(8+META_OFFSET);
        return NAME_backspace;
    }

    if ( count == 1 )
    { int c = buf[0] & 0xff;

      if ( event->xkey.state & MetaMask )	/* meta depressed */
	c += META_OFFSET;

      return toInt(c);
    }

    if ( count > 1 )			/* see above */
    { char *e;
      int c;

      if ( (e = utf8_get_char(buf, &c)) && e-buf == count )
      { DEBUG(NAME_event, Cprintf("\t-->UTF-8 sequence for %d\n", c));

	if ( event->xkey.state & MetaMask )	/* meta depressed */
	  c += META_OFFSET;

	return toInt(c);
      }
    }
  }

  switch(sym)
  { case XK_F1:		return NAME_keyTop_1;
    case XK_F2:		return NAME_keyTop_2;
    case XK_F3:		return NAME_keyTop_3;
    case XK_F4:		return NAME_keyTop_4;
    case XK_F5:		return NAME_keyTop_5;
    case XK_F6:		return NAME_keyTop_6;
    case XK_F7:		return NAME_keyTop_7;
    case XK_F8:		return NAME_keyTop_8;
    case XK_F9:		return NAME_keyTop_9;
    case XK_F10:	return NAME_keyTop_10;

    case XK_L1:		return NAME_keyLeft_1;
    case XK_L2:		return NAME_keyLeft_2;
    case XK_L3:		return NAME_keyLeft_3;
    case XK_L4:		return NAME_keyLeft_4;
    case XK_L5:		return NAME_keyLeft_5;
    case XK_L6:		return NAME_keyLeft_6;
    case XK_L7:		return NAME_keyLeft_7;
    case XK_L8:		return NAME_keyLeft_8;
    case XK_L9:		return NAME_keyLeft_9;
    case XK_L10:	return NAME_keyLeft_10;

    case XK_R1:		return NAME_keyRight_1;
    case XK_R2:		return NAME_keyRight_2;
    case XK_R3:		return NAME_keyRight_3;
    case XK_R4:		return NAME_keyRight_4;
    case XK_R5:		return NAME_keyRight_5;
    case XK_R6:		return NAME_keyRight_6;
    case XK_R7:		return NAME_keyRight_7;
    case XK_R8:		return NAME_keyRight_8;
    case XK_R9:		return NAME_keyRight_9;
    case XK_R10:	return NAME_keyRight_10;
    case XK_R11:	return NAME_keyRight_11;
    case XK_R12:	return NAME_keyRight_12;
    case XK_R13:	return NAME_keyRight_13;
    case XK_R14:	return NAME_keyRight_14;
    case XK_R15:	return NAME_keyRight_14;

/* Cursor motion */

#ifndef XK_Page_Up
#define XK_Page_Up XK_Prior
#endif
#ifndef XK_Page_Down
#define XK_Page_Down XK_Next
#endif

    case XK_Home:	return NAME_cursorHome;
    case XK_Left:	return NAME_cursorLeft;
    case XK_Up:		return NAME_cursorUp;
    case XK_Right:	return NAME_cursorRight;
    case XK_Down:	return NAME_cursorDown;
    case XK_Page_Up:	return NAME_pageUp;
    case XK_Page_Down:	return NAME_pageDown;
    case XK_End:	return NAME_end;
    case XK_Begin:	return NAME_begin;

/* Misc Functions */

    case XK_Select:	return NAME_select;
    case XK_Print:	return NAME_print;
    case XK_Execute:	return NAME_execute;
    case XK_Insert:	return NAME_insert;
    case XK_Undo:	return NAME_undo;
    case XK_Redo:	return NAME_redo;
    case XK_Menu:	return NAME_menu;
    case XK_Find:	return NAME_find;
    case XK_Cancel:	return NAME_cancel;
    case XK_Help:	return NAME_help;
    case XK_Break:	return NAME_break;
    case XK_Pause:	return NAME_pause;
  }

  DEBUG(NAME_keysym, Cprintf("sym = 0x%X\n", (unsigned int)sym));

  fail;
}


static Int
state_to_buttons(unsigned int state, Name name)
{ int r = 0;

  if ( state & Button1Mask )	r |= BUTTON_ms_left;
  if ( state & Button2Mask )	r |= BUTTON_ms_middle;
  if ( state & Button3Mask )	r |= BUTTON_ms_right;
  if ( state & Button4Mask )	r |= BUTTON_ms_button4;
  if ( state & Button5Mask )	r |= BUTTON_ms_button5;
  if ( state & ShiftMask )	r |= BUTTON_shift;
  if ( state & ControlMask )	r |= BUTTON_control;
  if ( state & MetaMask )	r |= BUTTON_meta;

  if ( name == NAME_msLeftDown )
    r |= BUTTON_ms_left;
  else if ( name == NAME_msMiddleDown )
    r |= BUTTON_ms_middle;
  else if ( name == NAME_msRightDown )
    r |= BUTTON_ms_right;
  else if ( name == NAME_msButton4Down )
    r |= BUTTON_ms_button4;
  else if ( name == NAME_msButton5Down )
    r |= BUTTON_ms_button5;
  else if ( name == NAME_msLeftUp )
    r &= ~BUTTON_ms_left;
  else if ( name == NAME_msMiddleUp )
    r &= ~BUTTON_ms_middle;
  else if ( name == NAME_msRightUp )
    r &= ~BUTTON_ms_right;
  else if ( name == NAME_msButton4Up )
    r &= ~BUTTON_ms_button4;
  else if ( name == NAME_msButton5Up )
    r &= ~BUTTON_ms_button5;

  return toInt(r);
}


static Name
button_to_name(int press, unsigned int button)
{ switch( button )
  { case Button1:	return press ? NAME_msLeftDown     : NAME_msLeftUp;
    case Button2:	return press ? NAME_msMiddleDown   : NAME_msMiddleUp;
    case Button3:	return press ? NAME_msRightDown    : NAME_msRightUp;
    case Button4:	return press ? NAME_msButton4Down  : NAME_msButton4Up;
    case Button5:	return press ? NAME_msButton5Down  : NAME_msButton5Up;
  }

  fail;
}



EventObj
CtoEvent(Any window, XEvent *event)	/* window or frame */
{ Time time;
  int state = 0;
  int x;
  int y;
  Name ctx_name = NULL;
  Any name, ctx = NULL;
  static BoolObj do_wheel;
  EventObj ev;

  if ( !do_wheel )
    do_wheel = getClassVariableValueClass(ClassEvent, NAME_x11WheelMouse);

  switch( event->xany.type )
  { case KeyPress:			/* Key pressed */
    { x     = event->xkey.x;
      y     = event->xkey.y;
      state = event->xkey.state;
      time  = event->xkey.time;
      name  = keycode_to_name(window, event);
      if ( name == FAIL )
        fail;

      break;
    }
    case ButtonPress:			/* Button pressed/released */
    case ButtonRelease:
    { x     = event->xbutton.x;
      y     = event->xbutton.y;
      state = event->xbutton.state;
      time  = event->xbutton.time;

      if ( do_wheel )			/* Map button4 and 5 to wheel */
      { if ( event->xbutton.button == Button4 ||
	     event->xbutton.button == Button5 )
	{ if ( event->xany.type == ButtonPress )
	  { name = NAME_wheel;
	    ctx_name = NAME_rotation;
	    if ( event->xbutton.button == Button4 )
	      ctx = toInt(120);
	    else
	      ctx = toInt(-120);
	    break;
	  }
	  fail;
	}
      }

      name  = button_to_name(event->xany.type == ButtonPress,
			     event->xbutton.button);
      if ( name == FAIL )
        fail;

      break;
    }
    case MotionNotify:			/* Pointer motion events */
    { x     = event->xmotion.x;
      y     = event->xmotion.y;
      state = event->xmotion.state;
      time  = event->xmotion.time;

      if ( state & Button1Mask )
        name = NAME_msLeftDrag;
      else if ( state & Button2Mask )
        name = NAME_msMiddleDrag;
      else if ( state & Button3Mask )
        name = NAME_msRightDrag;
      else
        name = NAME_locMove;

      break;
    }
    case EnterNotify:
    case LeaveNotify:
    { x     = event->xcrossing.x;
      y     = event->xcrossing.y;
      state = event->xcrossing.state;
      time  = event->xcrossing.time;

#     define AnyButtonMask (Button1Mask|Button2Mask|Button3Mask)

      if ( event->xany.type == EnterNotify )
      { name = (state & AnyButtonMask ? NAME_areaResume
				      : NAME_areaEnter);
      } else
      { name = (state & AnyButtonMask ? NAME_areaCancel
				      : NAME_areaExit);
      }

      break;
    }
   default:				/* unknown event: do not convert */
      fail;
  }

  setLastEventTime(time);

  ev = answerObject(ClassEvent,
		    name,
		    window,
		    toInt(x), toInt(y),
		    state_to_buttons(state, name),
		    EAV);
  if ( ctx_name )
    attributeObject(ev, ctx_name, ctx);

  return ev;
}


typedef struct
{ const char *name;
  unsigned int mask;
} modmask;

static const modmask modmasks[] =
{ { "mod1", Mod1Mask },
  { "mod2", Mod2Mask },
  { "mod3", Mod3Mask },
  { "mod4", Mod4Mask },
  { "mod5", Mod5Mask },
  { NULL, 0 }
};


status
metaModifierDisplay(DisplayObj d, Name name)
{ const char *s = strName(name);
  const modmask *mm = modmasks;

  for( ; mm->name; mm++)
  { if ( streq(s, mm->name) )
    { MetaMask = mm->mask;
      succeed;
    }
  }

  fail;
}


		 /*******************************
		 *      COMPATIBILITY HACKS	*
		 *******************************/

#if XT_REVISION <= 3

Status XSetWMProtocols (dpy, w, protocols, count)
    Display *dpy;
    Window w;
    Atom *protocols;
    int count;
{   static Atom a = None;

    if ( a == None )
	a = XInternAtom (dpy, "WM_PROTOCOLS", False);

    XChangeProperty (dpy, w, a, XA_ATOM, 32,
		     PropModeReplace, (unsigned char *) protocols, count);
    return True;
}

#endif

#if !defined(HAVE_XTPOPUPSPRINGLOADED) && !defined(__sun__)
					/* sun's X11R5 fails on AC_HAVE_FUNC */
					/* for X11 functions */

void XtPopupSpringLoaded (widget)
    Widget widget;
{
    _XtPopup(widget, XtGrabExclusive, True);
}

#endif




