/* Epoch functionality.
   Copyright (C) 1985-1995 Free Software Foundation.

This file is part of XEmacs.

XEmacs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

XEmacs is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with XEmacs; see the file COPYING.  If not, write to the Free
Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Synched up with: Not in FSF. */

#include <config.h>
#include "lisp.h"
#include "frame-x.h"
#include "objects-x.h"
#include "events.h"

Lisp_Object Qx_property_change, Qx_client_message, Qx_map, Qx_unmap;
Lisp_Object Vepoch_event, Vepoch_event_handler;

Lisp_Object
make_xresource (XID xid, Atom type)
{
  struct Lisp_X_Resource *xr =
    alloc_lcrecord (sizeof (struct Lisp_X_Resource), lrecord_x_resource);
  Lisp_Object val;

  xr->xid = xid;
  xr->type = type;
  XSETXRESOURCE (val, xr);

  return val;
}

/*
 * Epoch equivalent:  epoch::intern-atom
 */
DEFUN ("x-intern-atom", Fx_intern_atom, Sx_intern_atom, 1, 1, 0,
 "Convert a STRING or SYMBOL into an atom and return as an XRESOURCE.")
     (name)
     Lisp_Object name;
{
  /* !!#### This function has not been Mule-ized */
  Atom atom;
  char *data;

  if (SYMBOLP (name))
    data = (char *) string_data (XSYMBOL (name)->name);
  else
    {
      CHECK_STRING (name, 0);
      data = (char *) string_data (XSTRING (name));
    }

  atom = XInternAtom (FIXME_DISPLAY, data, False);

  return make_xresource (atom, XA_ATOM);
}

/*
 * Epoch equivalent:  epoch::unintern-atom
 */
DEFUN ("x-atom-name", Fx_atom_name, Sx_atom_name, 1, 1, 0,
  "Return the name of an X atom resource as a string.")
     (atom)
     Lisp_Object atom;
{
  /* !!#### This function has not been Mule-ized */
  char *atom_name;
  Lisp_Object val;

  CHECK_XRESOURCE (atom, 0);
  if (XXRESOURCE (atom)->type != XA_ATOM)
    error ("Resource is not an atom");

  atom_name = XGetAtomName (FIXME_DISPLAY, XXRESOURCE (atom)->xid);

  if (atom_name)
    {
      val = build_string (atom_name);
      xfree (atom_name);
    }
  else
    val = Qnil;

  return val;
}

/*
 * Epoch equivalent:  epoch::string-to-resource
 */
DEFUN ("x-string-to-x-resource", Fx_string_to_x_resource,
       Sx_string_to_x_resource, 2, 3, 0,
  "Convert a numeric STRING to an XRESOURCE.\n\
STRING is assumed to represent a 32-bit numer value. XRESOURCE must be\n\
an X atom.  Optional BASE argument should be a number between 2 and 36,\n\
specifying the base for converting STRING.")
     (string, type, base)
     Lisp_Object string, type, base;
{
  /* !!#### This function has not been Mule-ized */
  XID xid;
  struct Lisp_X_Resource *xr;
  char *ptr;
  int b;

  CHECK_STRING (string, 0);
  CHECK_XRESOURCE (type, 0);

  if (EQ (base, Qnil))
    b = 0;
  else
    {
      CHECK_INT (base, 0);
      b = XINT (base);
      check_int_range (b, 2, 36);
    }

  if (XXRESOURCE (type)->type != XA_ATOM)
    error ("Resource must be an atom");
  xr = XXRESOURCE (type);

  xid = (XID) strtol ((CONST char*) string_data (XSTRING (string)), &ptr, b);

  return ((ptr == (char *) string_data (XSTRING (string)))
	  ? Qnil
	  : make_xresource (xid, xr->xid));
}

/*
 * Epoch equivalent:  epoch::resource-to-type
 */
DEFUN ("x-resource-to-type", Fx_resource_to_type, Sx_resource_to_type, 1, 1, 0,
  "Return an x-resource of type ATOM whose value is the type of the argument")
     (resource)
     Lisp_Object resource;
{
  struct Lisp_X_Resource *xr;

  CHECK_XRESOURCE (resource, 0);
  xr = XXRESOURCE (resource);

  return make_xresource (xr->type, XA_ATOM);
}

/* internal crap stolen from Epoch */
static char LongToStringBuffer[33]; /* can't have statics inside functions! */
char *
long_to_string (unsigned long n, unsigned int base)
{
  /* !!#### This function has not been Mule-ized */
  char *digit = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  char *s = LongToStringBuffer + 32; /* at most 33 characters in binary */

  *s = 0;			/* terminate */
  while (n)			/* something there */
    {
    *--s = digit[n % base];		/* store bottom digit */
    n /= base;			/* shift right */
    }
  if (*s == 0) *--s = '0';		/* in case nothing was put in string */
  return s;
}

/*
 * Epoch equivalent:  epoch::resource-to-string
 */
DEFUN ("x-resource-to-string", Fx_resource_to_string, Sx_resource_to_string,
       1, 2, 0,
  "Convert the xid of RESOURCE to a numeric string.\n\
Optional BASE specifies the base for the conversion (2..36 inclusive)")
     (resource, base)
     Lisp_Object resource, base;
{
  /* !!#### This function has not been Mule-ized */
  int cbase = 10;

  CHECK_XRESOURCE (resource, 0);
  if (!NILP (base))
    {
      CHECK_INT (base, 0);
      cbase = XINT (base);
      check_int_range (cbase, 2, 36);
    }

  return build_string (long_to_string (XXRESOURCE (resource)->xid, cbase));
}

/*
 * Epoch equivalent:  epoch::xid-of-frame
 *
 * This differs from x-window-id in xfns.c in that its return value is an
 * x-resource rather than a string.
 */
DEFUN ("x-id-of-frame", Fx_id_of_frame, Sx_id_of_frame, 0, 1, 0,
  "Return ID of FRAME as an x-resource, or nil on error.")
     (frame)
     Lisp_Object frame;
{
  struct frame *f = get_x_frame (frame);

  return make_xresource (XtWindow (FRAME_X_SHELL_WIDGET (f)), XA_WINDOW);
}

/*
 * Epoch equivalent:  epoch::query-tree
*/
DEFUN ("x-query-tree", Fx_query_tree, Sx_query_tree, 0, 1, 0,
  "Return the portion of the window tree adjacent to FRAME.\n\
Return value is the list ( ROOT PARENT . CHILDREN ).  The FRAME arg\n\
can either be a frame object or an x-resource of type window.")
     (frame)
     Lisp_Object frame;
{
  Window win;
  Window root, parent, *children;
  unsigned int count;
  int retval;
  Lisp_Object val;

  if (XRESOURCEP (frame))
    {
      if (XXRESOURCE (frame)->type != XA_WINDOW)
	error ("Frame resource must be of type WINDOW");
      win = XXRESOURCE (frame)->xid;
    }
  else
    {
      win = XXRESOURCE (Fx_id_of_frame (frame))->xid;
    }

  retval =
    XQueryTree (FIXME_DISPLAY, win, &root, &parent, &children, &count);

  /* Thank you, X-Consortium. XQueryTree doesn't return Success like everyone
   * else, it returns 1. (Success is defined to be 0 in the standard header
   * files)
   */
  if (!retval) return Qnil;

  val = Qnil;
  while (count)
    val = Fcons (make_xresource (children[--count], XA_WINDOW), val);

  xfree (children);

  return Fcons (make_xresource (root, XA_WINDOW),
		Fcons ((parent
			? make_xresource (parent, XA_WINDOW)
			: Qnil),
		       val));
}

/* more internal crap stolen from Epoch */

static void
verify_vector_has_consistent_type (Lisp_Object vector)
{
  int i;			/* vector index */
  XID rtype;			/* Xresource type (if vector of Xresources) */
  int length;			/* vector length */
  struct Lisp_Vector *v = XVECTOR (vector);
  Lisp_Object *element;
  Lisp_Object sample;
  Lisp_Object type_obj;		/* base type of vector elements */

  sample = v->contents[0];
  type_obj = sample;
  if (XRESOURCEP (sample))
    rtype = XXRESOURCE (sample)->type;
  length = v->size;
  element = v->contents;

  for (i = 1; i < length; ++i, ++element)
    {
      QUIT;
      if ((XTYPE (*element) != XTYPE (type_obj))
	  || (LRECORDP (type_obj) &&
	      (XRECORD_LHEADER (*element)->implementation !=
	       XRECORD_LHEADER (type_obj)->implementation))
	  || (XRESOURCEP (type_obj) && rtype != XXRESOURCE (*element)->type))
	error ("Vector has inconsistent types");
    }
}

static void
verify_list_has_consistent_type (Lisp_Object list)
{
  Lisp_Object type_obj;
  XID rtype;			/* Xresource type (if vector of Xresources) */
  Lisp_Object temp = Fcar (list);

  type_obj = temp;
  if (XRESOURCEP (temp))
    rtype = XXRESOURCE (temp)->type;
  list = Fcdr (list);

  for ( ; !NILP (list) ; list = Fcdr (list))
    {
      QUIT;
      temp = Fcar (list);
      if ((XTYPE (temp) != XTYPE (type_obj))
	  || (LRECORDP (type_obj) &&
	      (XRECORD_LHEADER (temp)->implementation !=
	       XRECORD_LHEADER (type_obj)->implementation))
	  || (XRESOURCEP (type_obj) && rtype != XXRESOURCE (temp)->type))
	error ("List has inconsistent types");
    }
}

#define BYTESIZE 8
/* 16 bit types */
typedef short int int16;
typedef short unsigned int uint16;

/* the Calculate functions return allocated memory that must be free'd.
   I tried to use alloca, but that fails. Sigh.
*/
static void *
calculate_vector_property (Lisp_Object vector, unsigned long *count,
			   Atom *type, int *format)
{
  /* !!#### This function has not been Mule-ized */
  int length;
  unsigned int size,tsize;
  int i;
  struct Lisp_Vector *v;
  void *addr;

  v = XVECTOR (vector);
  *count = length = v->size;

  switch (XTYPE (v->contents[0]))
    {
    case Lisp_Int:
      *type = XA_INTEGER;
      if (*format != 8 && *format != 16) *format = 32;
      size = *format * length;
      addr = (void *) xmalloc (size);
      for ( i = 0 ; i < length ; ++i )
	switch (*format)
	  {
	  case 32 : ((int *)addr)[i] = XINT (v->contents[i]); break;
	  case 16 : ((int16 *)addr)[i] = XINT (v->contents[i]); break;
	  case 8 : ((char *)addr)[i] = XINT (v->contents[i]); break;
	  }
      break;

    case Lisp_Record:
      if (XRESOURCEP (v->contents[0]))
	{
	  size = BYTESIZE * sizeof (XID) * length;
	  *format = BYTESIZE * sizeof (XID);
	  *type = XXRESOURCE (v->contents[0])->type;
	  addr = (void *) xmalloc (size);
	  for ( i = 0 ; i < length ; ++i )
	    ( (XID *) addr) [i] = XXRESOURCE (v->contents[i])->xid;
	}
      break;

    case Lisp_String:
      *format = BYTESIZE * sizeof (char);
      *type = XA_STRING;
      for ( i=0, size=0 ; i < length ; ++i )
	size += string_length (XSTRING (v->contents[i])) + 1; /* include null */
      addr = (void *) xmalloc (size);
      *count = size;
      for ( i = 0 , size = 0 ; i < length ; ++i )
	{
	  tsize = string_length (XSTRING (v->contents[i])) + 1;
	  memmove (((char *) addr), string_data (XSTRING (v->contents[i])), tsize);
	  size += tsize;
	}
      break;

    default:
      error ("Invalid type for conversion");
    }
  return addr;
}

static void *
calculate_list_property (Lisp_Object list, unsigned long *count,
			 Atom *type, int *format)
{
  /* !!#### This function has not been Mule-ized */
  int length;
  unsigned int size, tsize;
  int i;
  Lisp_Object tlist,temp;
  void *addr;

  *count = length = XINT (Flength (list));

  switch (XTYPE (Fcar (list)))
    {
    case Lisp_Int:
      *type = XA_INTEGER;
      if (*format != 8 && *format != 16) *format = 32;
      size = *format * length;
      addr = (void *) xmalloc (size);
      for ( i = 0 ; i < length ; ++i, list = Fcdr (list))
	switch (*format)
	  {
	  case 32 : ((int *)addr)[i] = XINT (Fcar (list)); break;
	  case 16 : ((int16 *)addr)[i] = XINT (Fcar (list)); break;
	  case 8 : ((char *)addr)[i] = XINT (Fcar (list)); break;
	  }
      break;

    case Lisp_Record:
      if (XRESOURCEP (Fcar (list)))
	{
	  size = BYTESIZE * sizeof (XID) * length;
	  *format = BYTESIZE * sizeof (XID);
	  *type = XXRESOURCE (Fcar (list))->type;
	  addr = (void *) xmalloc (size);
	  for ( i = 0 ; i < length ; ++i, list = Fcdr (list))
	    ((XID *)addr)[i] = XXRESOURCE (Fcar (list))->xid;
	}
      break;

    case Lisp_String:
      *format = BYTESIZE * sizeof (char);
      *type = XA_STRING;
      for ( i=0, size=0 , tlist=list ; i < length ; ++i, tlist = Fcdr (tlist) )
	size += string_length (XSTRING (Fcar (tlist))) + 1; /* include null */
      addr = (void *) xmalloc (size);
      *count = size;
      for ( i=0, size=0, tlist=list ; i < length  ; ++i , tlist = Fcdr (tlist) )
	{
	  temp = Fcar (tlist);
	  tsize = string_length (XSTRING (temp)) + 1;
	  memmove (((char *) addr), string_data (XSTRING (temp)), tsize);
	  size += tsize;
	}
      break;

    default:
      error ("Invalid type for conversion");
    }
  return addr;
}

/* Returns whether the conversion was successful or not */
static int
convert_elisp_to_x (Lisp_Object value, void **addr, unsigned long *count,
		    Atom *type, int *format, int *free_storage)
{
  /* !!#### This function has not been Mule-ized */
  if (VECTORP (value))
    verify_vector_has_consistent_type (value);
  else if (CONSP (value))
    verify_list_has_consistent_type (value);

  *free_storage = 0;
  switch (XTYPE (value))
    {
    case Lisp_String:
      *format = BYTESIZE;
      *type = XA_STRING;
      *count = strlen ((CONST char *) string_data (XSTRING (value)))+1;
      *addr = (void *) string_data (XSTRING (value));
      break;

    case Lisp_Int:
      *type = XA_INTEGER;
      *count = 1;
      *free_storage = 1;
      *addr = (void *) xmalloc (sizeof (int));
      /* This is ugly -
       * we have to deal with the possibility of different formats
       */
      switch (*format)
	{
	default :
	case 32 : *format = 32; *((int *)(*addr)) = XINT (value); break;
	case 16 : *((int16 *)(*addr)) = XINT (value); break;
	case 8 :  *((char *)(*addr)) = XINT (value); break;
	}
      break;

    case Lisp_Record:
      if (XRESOURCEP (value))
	{
	  *format = sizeof (XID) * BYTESIZE;
	  *type = XXRESOURCE (value)->type;
	  *count = 1;
	  *addr = (void *) & (XXRESOURCE (value)->xid);
	}
      break;

    case Lisp_Cons:
      *addr = calculate_list_property (value,count,type,format);
      *free_storage = 1;	/* above allocates storage */
      break;

    case Lisp_Vector:
      *addr = calculate_vector_property (value,count,type,format);
      *free_storage = 1;	/* above allocates storage */
      break;

    default :
      error ("Improper type for conversion");
    }

  return 1;
}

static Lisp_Object
format_size_hints (XSizeHints *hints)
{
  Lisp_Object result;
  struct Lisp_Vector *v;

  result = Fmake_vector (make_number (6), Qnil);
  v = XVECTOR (result);

  /* ugly but straightforward - just step through the members and flags
   * and stick in the ones that are there
   */
  if (hints->flags & (PPosition|USPosition))
    v->contents[0] = Fcons (make_number (hints->x), make_number (hints->y));
  if (hints->flags & (PSize|USSize))
    v->contents[1] = Fcons (make_number (hints->width),
			   make_number (hints->height));
  if (hints->flags & PMinSize)
    v->contents[2] = Fcons (make_number (hints->min_width),
			   make_number (hints->min_height));
  if (hints->flags & PMaxSize)
    v->contents[3] = Fcons (make_number (hints->max_width),
			   make_number (hints->max_height));
  if (hints->flags & PResizeInc)
        v->contents[4] = Fcons (make_number (hints->width_inc),
                               make_number (hints->height_inc));
  if (hints->flags & PAspect)
    v->contents[5] = Fcons (make_number (hints->min_aspect.x),
			   Fcons (make_number (hints->min_aspect.y),
				 Fcons (make_number (hints->max_aspect.x),
				       make_number (hints->max_aspect.y))));

  return result;
}

static Lisp_Object
format_string_property (char *buffer, unsigned long count)
{
  /* !!#### This function has not been Mule-ized */
  Lisp_Object value = Qnil;		/* data */
  Lisp_Object temp;			/* temp value holder */
  int len;				/* length of current string */
  char *strend;

  while (count)
    {
      strend = memchr (buffer, 0, (int) count);
      len = strend ? strend - buffer : count;
      if (len)
	{
	  temp = make_string ((Bufbyte *) buffer, len);
	  value = Fcons (temp, value);
	}
      buffer = strend + 1;	/* skip null, or leaving loop if no null */
      count -= len + !!strend;
    }

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : Fnreverse (value));
}

static Lisp_Object
format_integer_32_property (long *buff, unsigned long count)
{
  Lisp_Object value = Qnil;	/* return value */
  while (count)
    value = Fcons (make_number (buff[--count]), value);

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
format_integer_16_property (int16 *buff, unsigned long count)
{
  Lisp_Object value = Qnil;	/* return value */

  while (count)
    value = Fcons (make_number (buff[--count]), value);

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
format_integer_8_property (char *buff, unsigned long count)
{
  Lisp_Object value = Qnil;	/* return value */

  while (count)
    value = Fcons (make_number (buff[--count]), value);

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
format_integer_property (void *buff, unsigned long count, int format)
{
  switch (format)
    {
    case 8:
      return format_integer_8_property ((char *) buff, count);
      break;
    case 16:
      return format_integer_16_property ((int16 *) buff, count);
      break;
    case 32:
      return format_integer_32_property ((long *) buff, count);
      break;
    default:
      return Qnil;
    }
}

static Lisp_Object
format_cardinal_32_property (unsigned long *buff, unsigned long count)
{
  Lisp_Object value = Qnil;	/* return value */

  while (count)
    value = Fcons (make_number (buff[--count]), value);

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
format_cardinal_16_property (uint16 *buff, unsigned long count)
{
  Lisp_Object value = Qnil;	/* return value */

  while (count)
    value = Fcons (make_number (buff[--count]), value);

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
format_cardinal_8_property (unsigned char *buff, unsigned long count)
{
  Lisp_Object value = Qnil;	/* return value */

  while (count)
    value = Fcons (make_number (buff[--count]), value);

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
format_cardinal_property (void *buff, unsigned long count, int format)
{
  switch (format)
    {
    case 8:
      return format_cardinal_8_property ((unsigned char *) buff, count);
      break;
    case 16:
      return format_cardinal_16_property ((uint16 *) buff, count);
      break;
    case 32:
      return format_cardinal_32_property ((unsigned long *) buff, count);
    default:
      return Qnil;
    }
}

static Lisp_Object
format_unknown_property (void *buff, unsigned long count, Atom type,
			 int format)
{
  Lisp_Object value = Qnil;	/* return value */

  switch (format)
    {
    case 32:
      {
	XID *xid = (XID *) buff;
	int non_zero = 0;
	while (count--)
	  if (non_zero || xid[count])
	    {
	      value = Fcons (make_xresource (xid[count],type), value);
	      non_zero = 1;
	    }
      }
      break;
    }

  return (NILP (Fcdr (value))
	  ? Fcar (value)
	  : value);
}

static Lisp_Object
convert_x_to_elisp (void *buffer, unsigned long count, Atom type, int format)
{
  /* !!#### This function has not been Mule-ized */
  Lisp_Object value = Qnil;

  switch (type)
    {
    case None:
      value = Qnil;
      break;
    case XA_STRING:
      value = format_string_property (buffer, count);
      break;
    case XA_INTEGER:
      value = format_integer_property ((long *) buffer, count, format);
      break;
    case XA_CARDINAL:
      value = format_cardinal_property ((unsigned long *) buffer,
					count, format);
      break;
    case XA_WM_SIZE_HINTS:
      value = format_size_hints ((XSizeHints *) buffer);
      break;
    default:
      value = format_unknown_property ((void *) buffer, count, type, format);
      break;
    }

  return value;
}

/* get a property given its atom, display, and window */
Lisp_Object
static raw_get_property (Display *dpy, Window win, Atom prop)
{
  /* !!#### This function has not been Mule-ized */
  Lisp_Object value = Qnil;
  Atom actual_type;
  int actual_format;
  unsigned char *buffer;
  unsigned long count, remaining;
  int zret;

  zret = XGetWindowProperty (dpy, win, prop,
			     0L, 1024L, False, AnyPropertyType,
			     &actual_type, &actual_format,
			     &count, &remaining, &buffer);

  /* If remaining is set, then there's more of the property to get.
     Let's just do the whole read again, this time with enough space
     to get it all. */
  if (zret == Success && remaining > 0)
    {
      xfree (buffer);
      zret = XGetWindowProperty (dpy, win, prop,
				 0L, 1024L + ((remaining + 3) / 4),
				 False, AnyPropertyType,
				 &actual_type, &actual_format,
				 &count, &remaining, &buffer);
    }

  if (zret != Success)
    return Qnil;		/* failed */

  value = convert_x_to_elisp (buffer, count, actual_type, actual_format);

  xfree (buffer);
  return value;
}

/*
 * Epoch equivalent:  epoch::get-property
 */
DEFUN ("x-get-property", Fx_get_property, Sx_get_property, 1, 2, 0,
       "Retrieve the X window property for a frame. Arguments are\n\
PROPERTY: must be a string or an X-resource of type ATOM.\n\
FRAME: (optional) If present, must be a frame object, a frame id, or\n\
and X-resource of type WINDOW. Defaults to the current frame.\n\
Returns the value of the property, or nil if the property couldn't\n\
be retrieved.")
     (name, frame)
     Lisp_Object name, frame;
{
  /* !!#### This function has not been Mule-ized */
  Atom prop = None;
  Display *dpy = FIXME_DISPLAY;
  Window win;
  
  if (XRESOURCEP (frame))
    {
      if (XXRESOURCE (frame)->type != XA_WINDOW)
	error ("Frame resource must be of type WINDOW");
      win = XXRESOURCE (frame)->xid;
    }
  else
    {
      struct frame *f = get_x_frame (frame);

      /* We can't use Fx_id_of_frame because it returns the xid of
	 the shell widget.  But the property change has to take place
	 on the edit widget in order for a PropertyNotify event to
	 be generated */
      win = XtWindow (FRAME_X_TEXT_WIDGET (f));
#if 0
      win = XXRESOURCE (Fx_id_of_frame (frame))->xid;
#endif
    }

  if (STRINGP (name))
    {
      prop = XInternAtom (dpy, string_data (XSTRING (name)), True);
    }
  else if (XRESOURCEP (name))
    {
      if (XXRESOURCE (name)->type != XA_ATOM)
	error ("Property must be an ATOM X-resource");
      prop = XXRESOURCE (name)->xid;
    }
  else
    error ("Property must be a string or X-resource ATOM");

  if (prop == None)
    return Qnil;

  /* now we have the atom, let's ask for the property! */
  return raw_get_property (dpy,win,prop);
}

static Lisp_Object
raw_set_property (Display *dpy, Window win, Atom prop, Lisp_Object value)
{
  /* !!#### This function has not been Mule-ized */
  Atom actual_type;		/* X type of items */
  int actual_format;		/* size of data items (8,16,32) */
  unsigned long count;		/* Number of data items */
  void* addr;			/* address of data item array */
  int zret;			/* X call return value */
  int free_storage;		/* set if addr points at non-malloc'd store */

  actual_format = 0;		/* don't force a particular format */
  convert_elisp_to_x (value, &addr, &count, &actual_type, &actual_format,
		      &free_storage);

  zret = XChangeProperty (dpy, win, prop, actual_type, actual_format,
			  PropModeReplace, (char *) addr, count);
  XFlush (dpy);

  if (free_storage)
    xfree (addr);

  return value;
}

DEFUN ("x-set-property", Fx_set_property, Sx_set_property, 2, 3, 0,
      "Set a named property for a frame. The first argument (required)\n\
is the name of the property. The second is the value to set the propery\n\
to. The third (optional) is the frame, default is\n\
the current frame.")
     (name, value, frame)
     Lisp_Object name, value, frame;
{
  /* !!#### This function has not been Mule-ized */
  Atom prop = None;		/* name of the property */
  Window win;			/* window to put property on */
  Display *dpy = FIXME_DISPLAY;		/* display for window */

  if (XRESOURCEP (frame))
    {
      if (XXRESOURCE (frame)->type != XA_WINDOW)
	error ("Frame resource must be of type WINDOW");
      win = XXRESOURCE (frame)->xid;
    }
  else
    {
      struct frame *f = get_x_frame (frame);

      /* We can't use Fx_id_of_frame because it returns the xid of
	 the shell widget.  But the property change has to take place
	 on the edit widget in order for a PropertyNotify event to
	 be generated */
      win = XtWindow (FRAME_X_TEXT_WIDGET (f));
#if 0
      win = XXRESOURCE (Fx_id_of_frame (frame))->xid;
#endif
    }

  /* parse the atom name, either a string or an actual atom */
  if (STRINGP (name))
    {
      prop = XInternAtom (dpy, string_data (XSTRING (name)), False);
    }
  else if (XRESOURCEP (name))
    {
      if (XXRESOURCE (name)->type != XA_ATOM)
	error ("Property must be an X-resource ATOM");
      prop = XXRESOURCE (name)->xid;
    }
  else
    error ("Property must be a string or X-resource ATOM");

  if (prop == None)
    return Qnil;

  /* that's it. Now set it */
  return raw_set_property (dpy, win, prop, value);
}

/*
 * Epoch equivalent:  epoch::send-client-message
 */
DEFUN ("x-send-client-message", Fx_send_client_message, Sx_send_client_message,
       1, 5, 0,
  "Send a client message to DEST, marking it as being from SOURCE.\n\
The message is DATA of TYPE with FORMAT.  If TYPE and FORMAT are omitted,\n\
they are deduced from DATA.  If SOURCE is nil, the current frame is used.")
     (dest, source, data, type, format)
     Lisp_Object dest, source, data, type, format;
{
  /* !!#### This function has not been Mule-ized */
  int actual_format = 0;
  Atom actual_type;
  unsigned long count;
  void *addr;
  int free_storage;
  XEvent ev;
  struct Lisp_X_Resource *xr;
  Lisp_Object result;

  /* find our destination first */
  if (XRESOURCEP (dest))
    {
      if (XXRESOURCE (dest)->type == XA_WINDOW)
	xr = XXRESOURCE (dest);
      else
	error ("Argument must be a frame or x-window-resource");
    }
  else
    {
      xr = XXRESOURCE (Fx_id_of_frame (dest));
    }

  /* find our source - all we need from this is the window id */
  if (XRESOURCEP (source))
    {
      if (XXRESOURCE (source)->type != XA_WINDOW)
	error ("X-resource must be a WINDOW");
      ev.xclient.window = XXRESOURCE (source)->xid;
    }
  else
    {
      ev.xclient.window = XXRESOURCE (Fx_id_of_frame (source))->xid;
    }

  /* check format before data, because it can cause the data format to vary */
  if (!NILP (format))
    {
      CHECK_INT (format, 0);
      actual_format = XINT (format);
      if (actual_format != 8 && actual_format != 16 && actual_format != 32)
	error ("Format must be 8, 16, or 32, or nil");
    }

  /* clear out any cruft */
  memset ((char *) &ev.xclient.data, 0, 20);

  /* look for the data */
  if (!NILP (data))
    {
      convert_elisp_to_x (data, &addr, &count, &actual_type, &actual_format,
			  &free_storage);
      if ((count * actual_format) > 20*8)
	{
	  if (free_storage)
	    xfree (addr);
	  error ("Data is too big to fit in a client message");
	}
      memmove (&ev.xclient.data, (char *)addr, count * (actual_format/8));
      if (free_storage)
	xfree (addr);
    }

  if (!NILP (type))
    {
      CHECK_XRESOURCE (type,0);
      if (XXRESOURCE (type)->type != XA_ATOM)
        error ("Resource for message type must be an atom");
      actual_type = XXRESOURCE (type)->xid;
    }
      
  ev.xany.type = ClientMessage;
  ev.xclient.message_type = actual_type;
  ev.xclient.format = actual_format;
  /* There's no better way to set the mask than to hard code the correct
   * width bit pattern. 1L<<24 == OwnerGrabButtonMask, is the largest
   * This is the word from the X-consortium.
   */
  result = (XSendEvent (FIXME_DISPLAY, xr->xid, False, (1L<<25)-1L,&ev)
	    ? Qt
	    : Qnil);
  XFlush (FIXME_DISPLAY);
  return result;
}

/*
 * These duplicate the needed functionality from the Epoch event handler.
 */
static Lisp_Object
read_client_message (XClientMessageEvent *cm)
{
  Lisp_Object result;

  if (!cm->format)	/* this is probably a sign of a bug somewhere else */
    result = Qnil;
  else
    result = Fcons (make_xresource (cm->message_type, XA_ATOM),
		    Fcons (make_xresource (cm->window, XA_WINDOW),
			   convert_x_to_elisp ((void *) cm->data.b,
					       (20*8)/cm->format,
					       cm->message_type,
					       cm->format)));

  return result;
}

static Lisp_Object
read_property_event (XPropertyEvent *pe, Lisp_Object frame)
{
  /* !!#### This function has not been Mule-ized */
  Lisp_Object result, value;
  struct frame *f = XFRAME (frame);
  Display *dpy = XtDisplay (FRAME_X_SHELL_WIDGET (f));
  char *atom_name;

  atom_name = XGetAtomName (dpy, pe->atom);

  /* didn't get a name, blow this one off */
  if (atom_name == (char *) 0)
    return Qnil;

  /* We can't use Fx_id_of_frame because it returns the xid of
     the shell widget.  But the property change has to take place
     on the edit widget in order for a PropertyNotify event to
     be generated */
  value = raw_get_property (dpy, XtWindow (FRAME_X_TEXT_WIDGET (f)),
			    pe->atom);
  result = Fcons (build_string (atom_name), value);

  xfree (atom_name);

  return result;
}

void
dispatch_epoch_event (XEvent *event, Lisp_Object type)
{
  /* This function can GC */
  struct Lisp_Vector *evp;
  struct frame *f;

  f = x_any_window_to_frame (get_device_from_display (event->xany.display),
			     eevent->xany.window);
  if (!f)
    {
      Vepoch_event = Qnil;
      return;
    }

  if (!VECTORP (Vepoch_event) || XVECTOR (Vepoch_event)->size < 3)
    Vepoch_event = Fmake_vector (make_number (3), Qnil);
  evp = XVECTOR (Vepoch_event);

  XSETFRAME (evp->contents[2], f);

  if (EQ (type, Qx_property_change))
    {
      evp->contents[0] = Qx_property_change;
      evp->contents[1] =
	read_property_event (&event->xproperty, evp->contents[2]);
    }
  else if (EQ (type, Qx_client_message))
    {
      evp->contents[0] = Qx_client_message;
      evp->contents[1] = read_client_message (&event->xclient);
    }
  else if (EQ (type, Qx_map))
    {
      evp->contents[0] = Qx_map;
      evp->contents[1] = Qt;
    }
  else if (EQ (type, Qx_unmap))
    {
      evp->contents[0] = Qx_unmap;
      evp->contents[1] = Qnil;
    }
  else
    {
      Vepoch_event = Qnil;
    }

  if (NILP (Vepoch_event))
    return;
  if (NILP (Vepoch_event_handler))
    return;

  Ffuncall (1, &Vepoch_event_handler);

  Vepoch_event = Qnil;
  return;
}

void
syms_of_epoch (void)
{
  defsubr (&Sx_intern_atom);
  defsubr (&Sx_atom_name);
  defsubr (&Sx_string_to_x_resource);
  defsubr (&Sx_resource_to_type);
  defsubr (&Sx_resource_to_string);
  defsubr (&Sx_id_of_frame);
  defsubr (&Sx_query_tree);
  defsubr (&Sx_get_property);
  defsubr (&Sx_set_property);
  defsubr (&Sx_send_client_message);

  defsymbol (&Qx_property_change, "x-property-change");
  defsymbol (&Qx_client_message, "x-client-message");
  defsymbol (&Qx_map, "x-map");
  defsymbol (&Qx_unmap, "x-unmap");
}

void
vars_of_epoch (void)
{
  Fprovide (intern ("epoch"));

  DEFVAR_LISP ("epoch-event-handler", &Vepoch_event_handler,
	       "If this variable is not nil, then it is assumed to have\n\
a function in it.  When an epoch event is received for a frame, this\n\
function is called.");
  Vepoch_event_handler = Qnil;

  DEFVAR_LISP ("epoch-event", &Vepoch_event,
	       "Bound to the value of the current event when epoch-event-handler is called.");
  Vepoch_event = Qnil;
}
