/* Copyright (C) 1997 Aladdin Enterprises.  All rights reserved.
  
  This file is part of GNU Ghostscript.
  
  GNU Ghostscript is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility to
  anyone for the consequences of using it or for whether it serves any
  particular purpose or works at all, unless he says so in writing.  Refer to
  the GNU General Public License for full details.
  
  Everyone is granted permission to copy, modify and redistribute GNU
  Ghostscript, but only under the conditions described in the GNU General
  Public License.  A copy of this license is supposed to have been given to
  you along with GNU Ghostscript so you can know your rights and
  responsibilities.  It should be in a file named COPYING.  Among other
  things, the copyright notice and this notice must be preserved on all
  copies.
  
  Aladdin Enterprises is not affiliated with the Free Software Foundation or
  the GNU Project.  GNU Ghostscript, as distributed by Aladdin Enterprises,
  does not depend on any other GNU software.

  However Display Ghostscript System depends on GLib. */

/* $Id: dgsdps.c,v 1.2 2000/02/28 19:31:31 cigas Exp $ */
/* Display Ghostscript dps and dpscap functions */

#include <glib.h>		/* For Hash tables */
#include "string_.h"

/* the X people can't make their headers define what they need automatically... */
#define NEED_REPLIES
#define NEED_EVENTS
#include "x_.h"			/* standard X include files */
#include "DPS/XDPSproto.h"
#include "DPSCAPproto.h"
#include "DPSCAP.h"
#include "DPS/XDPS.h"

/* network headers for the agent */
#include <sys/socket.h>
#include <netinet/in.h>		/* for ntohs */
#include <stdlib.h>

/* required by fork based server */
#include <signal.h>

#include "dgs.h"
#include "dgsdps.h"
#include "dgsmisc.h"
#include "dgsconn.h"
#include "dgsstrm.h"
#include "dgsagent.h"

/* --- dpscap_* --- */
/* --- dpscap_Conn --- */
private void dpscap_ConnSetup_display(xCAPConnSetupReq * req);
private void dpscap_ConnSetup_reply_success(xCAPConnSuccess * r, Window win);
private void dpscap_ConnSetup_reply_failed(xCAPConnFailed * r,
					   int reason_length);

int
dpscap_ConnSetup(gsdpsx_conn_info * conn, unsigned char *msg, int len)
{
    xCAPConnSetupReq *req = (xCAPConnSetupReq *) msg;
    int used;
    xCAPConnSuccess reply_success;
    xCAPConnFailed reply_failed;
    const char *reason;

    /* Intel: 'l', PowerPC and Sparc: 'B' */
    if ((conn->byteorder != 'l' && conn->byteorder != 'B')) {
	reason = "Wrong byte oroder";
	dpscap_ConnSetup_reply_failed(&reply_failed, strlen(reason));
	send(conn->sock, (const void *)&reply_failed, sizeof(xCAPConnFailed),
	     0);
	send(conn->sock, (const void *)reason, strlen(reason), 0);
	return -1;
    }

    used = sz_xCAPConnSetupReq;
    if (len - used < 0) {
	reason = "Request is too short";
	dpscap_ConnSetup_reply_failed(&reply_failed, strlen(reason));
	send(conn->sock, (const void *)&reply_failed, sizeof(xCAPConnFailed),
	     0);
	send(conn->sock, (const void *)reason, strlen(reason), 0);
	return -1;
    }

    /* Byteorder filtering */
    if (util_byteorder_check(conn)) {
	req->flags = ntohs(req->flags);
	req->libraryversion = ntohl(req->libraryversion);
	req->authProtoNameLength = ntohs(req->authProtoNameLength);
	req->authProtoDataLength = ntohs(req->authProtoDataLength);
	req->displayStringLength = ntohs(req->displayStringLength);
	req->nodeStringLength = ntohs(req->nodeStringLength);
	req->transportStringLength = ntohs(req->transportStringLength);
	req->display = ntohs(req->display);
	req->screen = ntohs(req->screen);
	req->reserved = ntohs(req->reserved);
	req->clientWindow = ntohl(req->clientWindow);
    }

    /* check that the display name is there */
    used += req->displayStringLength;
    if (len - used < 0 || req->displayStringLength > 255) {
	/* we can't deal with that */
	reason = "Wrong display name";
	dpscap_ConnSetup_reply_failed(&reply_failed, strlen(reason));
	send(conn->sock, (const void *)&reply_failed, sizeof(xCAPConnFailed),
	     0);
	send(conn->sock, (const void *)reason, strlen(reason), 0);
	return -1;
    }

    /* Set DISPLAY environment variable */
    dpscap_ConnSetup_display(req);
    conn->clientWindow = req->clientWindow;

    /* alright, time to send a reply back */
    dpscap_ConnSetup_reply_success(&reply_success, conn->agent_win);
    send(conn->sock, (const void *)&reply_success, reply_success.reasonLength,
	 0);

    /* initialize outputs */
    gsdpsx_std_init();


    return used;
}

private void
dpscap_ConnSetup_display(xCAPConnSetupReq * req)
{
    unsigned char *msg = (unsigned char *)req;
    int off;
    char displayname[256];
    gchar *env_pair;

    /* The display name might not be there and it certainly won't be NULL
       terminated.  We don't need to worry about overwriting what is after it */
    if (req->displayStringLength) {
	off = sz_xCAPConnSetupReq
	    + req->authProtoNameLength + req->authProtoDataLength;
	memcpy(displayname, msg + off, req->displayStringLength);
	displayname[req->displayStringLength] = '\0';
	if (DGS_DEBUG)
	    dprintf1("setting DISPLAY to '%s'\n", displayname);
	env_pair = g_strdup_printf("DISPLAY=%s", displayname);
	putenv(env_pair);
    }
}

private void
dpscap_ConnSetup_reply_failed(xCAPConnFailed * r, int reason_length)
{
    int swap = util_byteorder_check(&conn_info);

    r->success = FALSE;
    r->reasonLength = reason_length;
    if (swap) {
	r->additionalLength = htons(0);
	r->serverVersion = htonl(DPSPROTOCOLVERSION);
	r->dpscapVersion = DPSCAPPROTOVERSION;
	r->pad = 0;
	r->reserved = htons(0);
    } else {
	r->additionalLength = 0;
	r->serverVersion = DPSPROTOCOLVERSION;
	r->dpscapVersion = DPSCAPPROTOVERSION;
	r->pad = 0;
	r->reserved = 0;
    }
}

private void
dpscap_ConnSetup_reply_success(xCAPConnSuccess * r, Window win)
{
    int swap = util_byteorder_check(&conn_info);

    r->success = TRUE;
    r->reasonLength = sizeof(xCAPConnSuccess);
    /* This field is ignored by the client. */

    if (swap) {
	r->additionalLength = htons(0);
	r->serverVersion = htonl(DPSPROTOCOLVERSION);
	r->dpscapVersion = DPSCAPPROTOVERSION;
	r->reserved = 0;
	r->flagsUsed = htons(0);
	r->preferredNumberFormat = htonl(0);	/*! */
	r->floatingNameLength = htonl(0);	/*! whats this? */
	r->agentWindow = htonl(win);	/* only used to resume suspended contexts */
    } else {
	r->additionalLength = 0;
	r->serverVersion = DPSPROTOCOLVERSION;
	r->dpscapVersion = DPSCAPPROTOVERSION;
	r->reserved = 0;
	r->flagsUsed = 0;
	r->preferredNumberFormat = 0;	/*! */
	r->floatingNameLength = 0;	/*! whats this? */
	r->agentWindow = win;	/* only used to resume suspended contexts */
    }
}


/* --- dpscap_Notify --- */
private void dpscap_Notify_grab(gsdpsx_conn_info * conn, xCAPNotifyReq * req);
private void dpscap_Notify_ungrab(gsdpsx_conn_info * conn,

				  xCAPNotifyReq * req);
private void dpscap_Notify_freegc(gsdpsx_conn_info * conn,

				  xCAPNotifyReq * req);
private void dpscap_Notify_sync(gsdpsx_conn_info * conn, xCAPNotifyReq * req);
private void dpscap_Notify_pause(gsdpsx_conn_info * conn, xCAPNotifyReq * req,

				 enum scheduling_action *sched_act);

int
dpscap_Notify(gsdpsx_conn_info * conn, unsigned char *msg, int remaining,
	      enum scheduling_action *sched_act)
{
    xCAPNotifyReq *req = (xCAPNotifyReq *) msg;
    int swap = util_byteorder_check(conn);
    short length = req->length;

    /* Byteorder filtering (stage 1) */
    if (swap)
	length = ntohs(req->length);

    /* check for the whole message... must do first check to ensure
       that you can do the second */
    if (remaining < sz_xCAPNotifyReq || (length * 4 > remaining))
	return 0;

    /* Byteorder filtering (stage 2) */
    if (swap) {
	req->length = length;
	req->cxid = ntohl(req->cxid);
	req->notification = ntohl(req->notification);
	req->data = ntohl(req->data);
	req->extra = ntohl(req->extra);
    }

    switch (req->notification) {
	case DPSCAPNOTE_GRAB:
	    dpscap_Notify_grab(conn, req);
	    break;
	case DPSCAPNOTE_UNGRAB:
	    dpscap_Notify_ungrab(conn, req);
	    break;
	case DPSCAPNOTE_FREEGC:
	    dpscap_Notify_freegc(conn, req);
	    break;
	case DPSCAPNOTE_SYNC:
	    dpscap_Notify_sync(conn, req);
	    break;
	case DPSCAPNOTE_PAUSE:
	    dpscap_Notify_pause(conn, req, sched_act);
	    break;
	default:
	    dprintf1("ignoring unknown xCAPNotify %X\n",
		     (unsigned)req->notification);
    }
    return (req->length) * 4;
}

private void
dpscap_Notify_grab(gsdpsx_conn_info * conn, xCAPNotifyReq * req)
{
    /* TODO */
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] Notify->grab is not implemented\n");
}

private void
dpscap_Notify_ungrab(gsdpsx_conn_info * conn, xCAPNotifyReq * req)
{
    /* TODO */
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] Notify->ungrab is not implemented\n");
}
private void
dpscap_Notify_freegc(gsdpsx_conn_info * conn, xCAPNotifyReq * req)
{
    /* TODO */
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] Notify->freegc is not implemented\n");
}

private void
dpscap_Notify_sync(gsdpsx_conn_info * conn, xCAPNotifyReq * req)
{

    XEvent event;
    Display * curr_dpy = DGS_get_current_display();
    

    event.xclient.type = ClientMessage;
    event.xclient.serial = req->data;
    event.xclient.send_event = True;
    event.xclient.message_type =
	XInternAtom(curr_dpy, DPSCAP_TYPE_SYNC, False);
    event.xclient.format = 8;

    if (util_byteorder_check(conn))
	event.xclient.data.l[0] = htonl(req->data);
    else
	event.xclient.data.l[0] = req->data;

    event.xclient.display = curr_dpy;
    event.xclient.window = conn->clientWindow;
    if (0)
	g_message("[%s]", "dpscap_Notify_sync");
    XSendEvent(event.xclient.display, event.xclient.window, False, 0, &event);
    XSync(event.xclient.display, False);
}

private void dpscap_Notify_pause_check_callback_set(gpointer data,
						    gpointer user_data);
private void dpscap_Notify_pause_remove(GSet ** base_set, GSet ** remove_set);
private Bool dpscap_Notify_pause_check_callback_x(Display * xdpy,
						  XEvent * event,
						  XPointer arg);
private void
dpscap_Notify_pause(gsdpsx_conn_info * conn, xCAPNotifyReq * req,
		    enum scheduling_action *sched_act)
{
    /* a PAUSE requires that we wait until we receive an X event
       (on the X comm link) to resume.  This ensures that X has caught up
       with the window creation or other changes */
    /* XEvent event; */
    conn->pause_cxids_set =
	g_set_add(conn->pause_cxids_set, (gpointer) req->cxid);
    /*! yielding to other threads here would be nice, 
       but this will require some fancy work */
    /*! this could eat the event from another context... 
       we need to be careful of that,
       either by creating a different window per connection, or 
       waking the other up */
    /*! we might actually be okay, it turns out */
    /* XIfEvent(shared_dpy, &event, 
       dpscap_Notify_pause_check_callback_x,
       (XPointer)req->cxid); */
    if (!dpscap_Notify_pause_check(&conn_info, req->cxid, 0)) {
	int next = 0;
	int steady = 1;
	int cpsid = (int)g_hash_table_lookup(conn->cxid_to_cpsid_table,
					     (gpointer) req->cxid);

	if (!status_send_if_masked(conn, cpsid, PSFROZEN, next))
	    status_send_if_masked(conn, cpsid, PSFROZEN, steady);
	*sched_act = SCHED_ACT_YIELD;
    }
}

Bool dpscap_Notify_pause_check(gsdpsx_conn_info * conn, int cxid, int sync)
{
    GSet *remove_set = NULL;
    XEvent event;
    Bool resutl;

    if (!(conn->pause_cxids_set))
	return True;
    if (g_set_count(conn->pause_cxids_set) == 0)
	return True;

    if (cxid) {
	if (sync) {
	    XIfEvent(shared_dpy,
		     &event,
		     dpscap_Notify_pause_check_callback_x, (XPointer) cxid);
	    conn->pause_cxids_set = g_set_remove(conn->pause_cxids_set,
						 (gpointer) cxid);
	    return True;
	} else {
	    resutl = XCheckIfEvent(shared_dpy,
				   &event,
				   dpscap_Notify_pause_check_callback_x,
				   (XPointer) cxid);
	    if (resutl)
		conn->pause_cxids_set = g_set_remove(conn->pause_cxids_set,
						     (gpointer) cxid);
	    return resutl;
	}
    } else {
	if (sync)
	    return False;	/* What should I do? */
	else {
	    g_set_foreach(conn->pause_cxids_set,
			  dpscap_Notify_pause_check_callback_set,
			  &remove_set);
	    if (g_set_count(remove_set) == 0)
		return False;
	    dpscap_Notify_pause_remove(&(conn->pause_cxids_set), &remove_set);
	    return True;
	}

    }
}
private void
dpscap_Notify_pause_check_callback_set(gpointer data, gpointer user_data)
{
    XEvent event;
    CONTEXTXID cxid = (CONTEXTXID) data;
    Bool result = XCheckIfEvent(shared_dpy,
				&event,
				dpscap_Notify_pause_check_callback_x,
				(XPointer) cxid);

    if (result)
	*((GSet **) user_data) = g_set_add(*((GSet **) user_data),
					   (gpointer) cxid);

}
/* checks to see if the event is a resume on the cxid 
 Was: WaitForResumeProc */
private Bool
dpscap_Notify_pause_check_callback_x(Display * xdpy, XEvent * event,
				     XPointer arg)
{

    if (event->xany.window == conn_info.agent_win
	&& event->type == ClientMessage
	&& event->xclient.message_type == conn_info.resume_atom
	&& event->xclient.format == 32
	&& event->xclient.data.l[0] == (CONTEXTXID) arg)
	/* the commented out is context X id... we don't support 
	   multiple contexts! */
    {
	/*dprintf("got RESUME Message\n"); */
	return True;
    } else
	return False;
}
private void
dpscap_Notify_pause_remove(GSet ** base_set, GSet ** remove_set)
{
    GSList *slist = (GSList *) * remove_set;
    guint length = g_slist_length(slist);
    int i;
    CONTEXTXID cxid;

    for (i = 0; i < length; i++) {
	cxid = (CONTEXTXID) g_slist_nth_data(slist, i);
	*base_set = g_set_remove(*base_set, (gpointer) cxid);
    }
    for (i = 0; i < length; i++) {
	cxid = (CONTEXTXID) g_slist_nth_data(slist, 0);
	slist = g_slist_remove(slist, (gpointer) cxid);
    }
    *remove_set = (GSet *) slist;
}

/* --- dpscap_SetArg --- */
int
dpscap_SetArg(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* Still ... TODO */
    xCAPSetArgReq *req = (xCAPSetArgReq *) pmsg;
    short length = req->length;
    int result;
    int swap = util_byteorder_check(conn);

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xCAPSetArgReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->arg = ntohl(req->arg);
	req->val = ntohl(req->val);
    }
    switch (req->arg) {
	case AGENT_ARG_SMALLFONTS:
	    switch (req->val) {
		case AGENT_SMALLFONTS_ACCURATE:
		case AGENT_SMALLFONTS_FAST:
		    break;
		default:
		    fprintf(stderr,
			    "Wrong value for AGENT_ARG_SMALLFONTS in dpscap_SetArg\n");
	    }
	    break;
	case AGENT_ARG_PIXMEM:
	    switch (req->val) {
		case AGENT_PIXMEM_LIMITED:
		case AGENT_PIXMEM_MODERATE:
		case AGENT_PIXMEM_UNLIMITED:
		    break;
		default:
		    fprintf(stderr,
			    "Wrong value for AGENT_ARG_PIXMEM in dpscap_SetArg\n");
	    }
	    break;
	default:
	    fprintf(stderr,
		    "Wrong argument specification in dpscap_SetArg\n");
    }
    return result;
}

/* --- dpscap function stubs (not implemented) --- */
int
dpscap_FlushAgent(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* TODO */
    xCAPFlushAgentReq *req = (xCAPFlushAgentReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] FlushAgent is not implemented\n");
    if (remaining < sz_xCAPFlushAgentReq || (result > remaining))
	return 0;
    else
	return result;
}


/* --- dps_CreateContext --- */
int
dps_CreateContext(gsdpsx_conn_info * conn,
		  unsigned char *msg, int remaining,
		  enum scheduling_action *sched_act)
{
    xPSCreateContextReq *req = (xPSCreateContextReq *) msg;
    int swap = util_byteorder_check(conn);
    short length = req->length;

    /* Byteorder filtering (stage 1) */
    if (swap)
	length = ntohs(req->length);
    /* check for the whole message... must do first check to ensure
       that you can do the second */
    if (remaining < sz_xPSCreateContextReq || (length * 4 > remaining))
	return 0;

    /* Byteorder filtering (stage 2) */
    if (swap) {
	req->length = length;
	req->cxid = ntohl(req->cxid);
	req->sxid = ntohl(req->sxid);	/* Not used yet */
	req->drawable = ntohl(req->drawable);
	req->gc = ntohl(req->gc);	/* Not used yet */
	req->x = ntohs(req->x);
	req->y = ntohs(req->y);
	req->eventmask = ntohl(req->eventmask);	/* Not used yet */
	req->cmap = ntohl(req->cmap);	/* Not used yet */
	req->redmax = ntohl(req->redmax);	/* Not used yet */
	req->redmult = ntohl(req->redmult);	/* Not used yet */
	req->greenmax = ntohl(req->greenmax);	/* Not used yet */
	req->greenmult = ntohl(req->greenmult);	/* Not used yet */
	req->bluemax = ntohl(req->bluemax);	/* Not used yet */
	req->bluemult = ntohl(req->bluemult);	/* Not used yet */
	req->colorbase = ntohl(req->colorbase);	/* Not used yet */
	req->graymax = ntohl(req->graymax);	/* Not used yet */
	req->graymult = ntohl(req->graymult);	/* Not used yet */
	req->graybase = ntohl(req->graybase);	/* Not used yet */
	req->actual = ntohl(req->actual);	/* Not used yet */
    }

    /* cxid, XID context */
    conn->ctx_init_parameter.cxid = req->cxid,
	/* set device drawable */
	conn->ctx_init_parameter.drawable = req->drawable;

    /* initial matrix, should it be needed */
    conn->ctx_init_parameter.offset_x = req->x;
    conn->ctx_init_parameter.offset_y = req->y;

    *sched_act = SCHED_ACT_FORK;
    return (req->length) * 4;
}

void
dps_CreateContext_reply(gsdpsx_conn_info * conn, int cpsid)
{
    xPSCreateContextReply r;
    int swap = util_byteorder_check(conn);

    r.type = X_Reply;
    if (swap) {
	r.sequenceNumber = htons(0);	/* whoop.  not in a server */
	r.length = htonl((sizeof(r) - sz_xGenericReply) / 4);
	r.cpsid = htonl(cpsid);
    } else {
	r.sequenceNumber = 0;	/* whoop.  not in a server */
	r.length = (sizeof(r) - sz_xGenericReply) / 4;
	r.cpsid = cpsid;
    }
    send(conn->sock, (void *)&r, sizeof(r), 0);
}

/* --- dps_GiveInput --- */
int
dps_GiveInput(gsdpsx_conn_info * conn,
	      unsigned char *pmsg, int remaining,
	      enum scheduling_action *sched_act)
{
    xPSGiveInputReq *req = (xPSGiveInputReq *) pmsg;
    int used;
    short length = req->length;
    int cpsid = gsdpsx_get_current_context_index();
    int new_cpsid;
    CONTEXTXID cxid =
	(CONTEXTXID) g_hash_table_lookup(conn->cpsid_to_cxid_table,
					 (void *)cpsid);
    int swap = util_byteorder_check(conn);
    int req_cxid_backup;
    int req_cxid_swapped;

    /* Byteorder filtering (stage 1) */
    if (swap)
	length = ntohs(req->length);

    used = length * 4;

    if (remaining < used) {
	/* get the rest of the data */
	if (0)
	    dprintf2("incomplete message, remaining is %d, needed %d\n",
		     (int)remaining, (int)used);
	return 0;
    }

    /* Byteorder filtering (stage 1.5) */
    if (swap) {
	req_cxid_backup = req->cxid;
	req_cxid_swapped = ntohl(req->cxid);
    } else {
	req_cxid_backup = req->cxid;
	req_cxid_swapped = req->cxid;
    }

    req->cxid = req_cxid_swapped;
    if (req->cxid != cxid) {
	new_cpsid = (int)g_hash_table_lookup(conn->cxid_to_cpsid_table,
					     (void *)req->cxid);
	*sched_act = SCHED_ACT_YIELD;
	gsdpsx_request_context_index(new_cpsid);
	req->cxid = req_cxid_backup;
	return 0;
    }
    req->cxid = req_cxid_backup;

    if (swap) {
	req->length = length;
	req->nunits = ntohs(req->nunits);
	req->pad = ntohs(req->pad);
	req->cxid = req_cxid_swapped;
    }

    buf_input_set(conn->buf, (char *)(req + 1), req->nunits);
    return used;
}

private void dps_XIDFromContext_reply(gsdpsx_conn_info * conn,

				      CONTEXTXID cxid);

int
dps_XIDFromContext(gsdpsx_conn_info * conn,
		   unsigned char *pmsg, int remaining)
{
    xPSXIDFromContextReq *req = (xPSXIDFromContextReq *) pmsg;
    CONTEXTXID cxid;
    int swap = util_byteorder_check(conn);
    int length = req->length;
    int result;

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xPSXIDFromContextReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->cpsid = ntohl(req->cpsid);
    }
    cxid = (CONTEXTXID) g_hash_table_lookup(conn->cpsid_to_cxid_table,
					    (void *)req->cpsid);
    dps_XIDFromContext_reply(conn, cxid);
    return result;
}

private void
dps_XIDFromContext_reply(gsdpsx_conn_info * conn, CONTEXTXID cxid)
{
    xPSXIDFromContextReply r;
    int swap = util_byteorder_check(conn);

    r.type = X_Reply;
    if (swap) {
	r.sequenceNumber = htons(0);	/* TODO */
	r.length = htonl((sizeof(r) - sz_xGenericReply) / 4);
	r.cxid = htonl(cxid);
	r.sxid = htonl(0);	/* TODO */
    } else {
	r.sequenceNumber = 0;	/* TODO */
	r.length = (sizeof(r) - sz_xGenericReply) / 4;
	r.cxid = cxid;
	r.sxid = 0;		/* TODO */
    }
    send(conn->sock, (void *)&r, sizeof(r), 0);
}

private void dps_ContextFromXID_reply(gsdpsx_conn_info * conn, int cpsid);
int
dps_ContextFromXID(gsdpsx_conn_info * conn,
		   unsigned char *pmsg, int remaining)
{
    xPSContextFromXIDReq *req = (xPSContextFromXIDReq *) pmsg;
    int cpsid;
    int swap = util_byteorder_check(conn);
    int length = req->length;
    int result;

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xPSContextFromXIDReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->cxid = ntohl(req->cxid);
    }

    cpsid = (int)g_hash_table_lookup(conn->cxid_to_cpsid_table,
				     (void *)req->cxid);
    dps_ContextFromXID_reply(conn, cpsid);
    return result;
}
private void
dps_ContextFromXID_reply(gsdpsx_conn_info * conn, int cpsid)
{
    xPSContextFromXIDReply r;
    int swap = util_byteorder_check(conn);

    r.type = X_Reply;
    if (swap) {
	r.sequenceNumber = htons(0);	/* TODO */
	r.length = htonl((sizeof(r) - sz_xGenericReply) / 4);
	r.cpsid = htonl((CARD32) cpsid);
    } else {
	r.sequenceNumber = 0;	/* TODO */
	r.length = (sizeof(r) - sz_xGenericReply) / 4;
	r.cpsid = (CARD32) cpsid;
    }
    send(conn->sock, (void *)&r, sizeof(r), 0);
}


/* --- dps_CreateContextFromID --- */
private void dps_CreateContextFromXID_reply(gsdpsx_conn_info * conn);

int
dps_CreateContextFromID(gsdpsx_conn_info * conn,
			unsigned char *pmsg, int remaining)
{
    xPSCreateContextFromIDReq *req = (xPSCreateContextFromIDReq *) pmsg;
    short length = req->length;
    int result;
    int swap = util_byteorder_check(conn);

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xPSCreateContextFromIDReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->cpsid = ntohl(req->cpsid);
	req->cxid = ntohl(req->cxid);
    }

    g_hash_table_insert(conn->cxid_to_cpsid_table,
			(void *)(req->cxid), (void *)(req->cpsid));
    g_hash_table_insert(conn->cpsid_to_cxid_table,
			(void *)(req->cpsid), (void *)(req->cxid));

    dps_CreateContextFromXID_reply(conn);
    return result;
}

private void
dps_CreateContextFromXID_reply(gsdpsx_conn_info * conn)
{
    xPSCreateContextFromIDReply r;
    int swap = util_byteorder_check(conn);

    r.type = X_Reply;
    if (swap) {
	r.sequenceNumber = htons(0);	/* TODO */
	r.length = htonl((sizeof(r) - sz_xGenericReply) / 4);
	r.sxid = htonl(0);	/* TODO */
    } else {
	r.sequenceNumber = 0;	/* TODO */
	r.length = (sizeof(r) - sz_xGenericReply) / 4;
	r.sxid = 0;
    }
    send(conn->sock, (void *)&r, sizeof(r), 0);
}


/* --- dps_GetStatus --- */
private void dps_GetStatus_reply(gsdpsx_conn_info * conn, CONTEXTXID cxid);
int
dps_GetStatus(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    xPSGetStatusReq *req = (xPSGetStatusReq *) pmsg;
    short length = req->length;
    int result;
    int swap = util_byteorder_check(conn);

    if (swap);
    length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xPSGetStatusReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->cxid = ntohl(req->cxid);
    }
    dps_GetStatus_reply(conn, req->cxid);
    return result;
}

private void
dps_GetStatus_reply(gsdpsx_conn_info * conn, CONTEXTXID cxid)
{
    xPSGetStatusReply r;
    int swap = util_byteorder_check(conn);
    int current_cpsid = gsdpsx_get_current_context_index();
    int cpsid = (CONTEXTXID) g_hash_table_lookup(conn->cxid_to_cpsid_table,
						 (void *)cxid);

    r.type = X_Reply;

    if (swap) {
	r.sequenceNumber = htons(0);
	r.length = htonl((sizeof(r) - sz_xGenericReply) / 4);
    } else {
	r.sequenceNumber = htons(0);
	r.length = (sizeof(r) - sz_xGenericReply) / 4;
    }
    /* TODO:
       PSSTATUSERROR PSRUNNING PSNEEDSINPUT PSZOMBIE PSFROZEN */
    if (g_set_member(conn->pause_cxids_set, (gpointer) cxid))
	r.status = PSFROZEN;
    else if (cpsid == current_cpsid)
	r.status = PSNEEDSINPUT;
    else if (g_set_member(conn->kill_cpsids_set, (gpointer) cpsid))
	r.status = PSZOMBIE;
    else
	r.status = PSRUNNING;
    send(conn->sock, (void *)&r, sizeof(r), 0);
}

/* --- dps_SetStatusMask --- */
private void dps_SetStatusMask_dump(const char *header, int mask);
int
dps_SetStatusMask(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSSetStatusMaskReq *req = (xPSSetStatusMaskReq *) pmsg;
    short length = req->length;
    int result;
    int swap = util_byteorder_check(conn);
    int cpsid;
    int mask;

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xPSSetStatusMaskReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->cxid = ntohl(req->cxid);
	req->enableMask = ntohl(req->enableMask);
	req->disableMask = ntohl(req->disableMask);
	req->nextMask = ntohl(req->nextMask);
    }

    cpsid = (int)g_hash_table_lookup(conn->cxid_to_cpsid_table,
				     (gpointer) (req->cxid));
    /* Mask */
    mask = (int)g_hash_table_lookup(conn->cpsid_to_mask_table,
				    (gpointer) cpsid);
    mask |= req->enableMask;
    mask &= ~req->disableMask;
    g_hash_table_insert(conn->cpsid_to_mask_table,
			(gpointer) cpsid, (gpointer) mask);
    if (DGS_DEBUG)
	dps_SetStatusMask_dump("mask: ", mask);

    /* Next mask */
    g_hash_table_insert(conn->cpsid_to_nextMask_table,
			(gpointer) cpsid, (gpointer) (req->nextMask));
    if (DGS_DEBUG)
	dps_SetStatusMask_dump("next mask: ", req->nextMask);

    return result;
}

private void
dps_SetStatusMask_dump(const char *header, int mask)
{
    fprintf(stderr, "%s: \n", header);
    fprintf(stderr, "  PSRUNNINGMASK: %s\n",
	    (mask & PSRUNNINGMASK) ? "on" : "off");
    fprintf(stderr, "  PSNEEDSINPUTMASK: %s\n",
	    (mask & PSNEEDSINPUTMASK) ? "on" : "off");
    fprintf(stderr, "  PSZOMBIEMASK: %s\n",
	    (mask & PSZOMBIEMASK) ? "on" : "off");
    fprintf(stderr, "  PSFROZENMASK: %s\n",
	    (mask & PSFROZENMASK) ? "on" : "off");
}


/* --- dps functions stubs (not implemented) --- */
private void dps_NotifyContext_kill(gsdpsx_conn_info * conn, CONTEXTXID cxid);
private void dps_NotifyContext_interrupt(gsdpsx_conn_info * conn,

					 CONTEXTXID cxid);
private void dps_NotifyContext_unfreeze(gsdpsx_conn_info * conn,

					CONTEXTXID cxid);
int
dps_NotifyContext(gsdpsx_conn_info * conn,
		  unsigned char *pmsg, int remaining,
		  enum scheduling_action *sched_act)
{
    xPSNotifyContextReq *req = (xPSNotifyContextReq *) pmsg;
    short length = req->length;
    int result;
    int swap = util_byteorder_check(conn);

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xPSNotifyContextReq || (result > remaining))
	result = 0;

    if (swap) {
	req->length = length;
	req->cxid = ntohl(req->cxid);
    }
    switch (req->notifyType) {
	case PSKILL:
	    dps_NotifyContext_kill(conn, req->cxid);
	    *sched_act = SCHED_ACT_DETACH;
	    break;
	case PSINTERRUPT:
	    dps_NotifyContext_interrupt(conn, req->cxid);
	    break;
	case PSUNFREEZE:
	    dps_NotifyContext_unfreeze(conn, req->cxid);
	    break;
    }
    return result;
}
private void
dps_NotifyContext_kill(gsdpsx_conn_info * conn, CONTEXTXID cxid)
{
    int cpsid = (int)g_hash_table_lookup(conn->cxid_to_cpsid_table,
					 (gpointer) cxid);

    /* gs_nulldevice(igs); */
    conn->kill_cpsids_set =
	g_set_add(conn->kill_cpsids_set, (gpointer) cpsid);
    {
	int next = 0;
	int steady = 1;

	if (!status_send_if_masked(conn, cpsid, PSZOMBIE, next))
	    status_send_if_masked(conn, cpsid, PSZOMBIE, steady);
    }
}
private void
dps_NotifyContext_interrupt(gsdpsx_conn_info * conn, CONTEXTXID cxid)
{
    if (DGS_DEBUG)
	fprintf(stderr,
		"[TODO] NotifyContext->interrupt is not implemented\n");
}
private void
dps_NotifyContext_unfreeze(gsdpsx_conn_info * conn, CONTEXTXID cxid)
{
    if (DGS_DEBUG)
	fprintf(stderr,
		"[TODO] NotifyContext->unfreeze is not implemented\n");
}
int
dps_DestroySpace(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSDestroySpaceReq *req = (xPSDestroySpaceReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] DestroySpace is not implemented\n");
    if (remaining < sz_xPSDestroySpaceReq || (result > remaining))
	result = 0;
    return result;
}

int
dps_Init(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSInitReq *req = (xPSInitReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] Init is not implemented\n");
    if (remaining < sz_xPSInitReq || (result > remaining))
	result = 0;
    return result;
}

int
dps_CreateSpace(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSCreateSpaceReq *req = (xPSCreateSpaceReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] CreateSpace is not implemented\n");
    if (remaining < sz_xPSCreateSpaceReq || (result > remaining))
	result = 0;
    return result;
}

int
dps_Reset(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSResetReq *req = (xPSResetReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] Reset is not implemented\n");
    if (remaining < sz_xPSResetReq || (result > remaining))
	result = 0;
    return result;
}

int
dps_CreateSecureContext(gsdpsx_conn_info * conn,
			unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSCreateSecureContextReq *req = (xPSCreateSecureContextReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] SecureContext is not implemented\n");
    if (remaining < sz_xPSCreateSecureContextReq || (result > remaining))
	result = 0;
    return result;
}

int
dps_NotifyWhenReady(gsdpsx_conn_info * conn,
		    unsigned char *pmsg, int remaining)
{
    /* TODO */
    xPSNotifyWhenReadyReq *req = (xPSNotifyWhenReadyReq *) pmsg;
    short length = req->length;
    int result;

    if (util_byteorder_check(conn))
	length = ntohs(req->length);

    result = length * 4;
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] NotifyWhenReady is not implemented\n");
    if (remaining < sz_xPSNotifyWhenReadyReq || (result > remaining))
	result = 0;
    return result;
}

/* --- dpsX functions stubs (not implemented) --- */
int
dpsx_ChangeGC(gsdpsx_conn_info * conn, unsigned char *pmsg, int remaining)
{
    xChangeGCReq *req = (xChangeGCReq *) pmsg;
    int length = req->length;
    int swap = util_byteorder_check(conn);
    int result;
    unsigned int *values;

    if (swap)
	length = ntohs(req->length);

    result = length * 4;
    if (remaining < sz_xChangeGCReq || (result > remaining))
	return 0;

    if (swap) {
	req->length = length;
	req->gc = ntohl(req->gc);
	req->mask = ntohl(req->mask);
    }

    values = (unsigned int *)(req + 1);
    if (DGS_DEBUG)
	fprintf(stderr, "[TODO] ChangeGC is not implemented\n");
    return result;
}
/* --- status --- */
private int status_check_mask(int status, int mask);
private int status_to_mask(int status);
private void status_send(gsdpsx_conn_info * conn, int cpsid, int status);
int
status_send_if_masked(gsdpsx_conn_info * conn,
		      int cpsid, int status, int steady1_or_next0)
{
    GHashTable *table = (steady1_or_next0) ?

	conn->cpsid_to_mask_table : conn->cpsid_to_nextMask_table;
    int mask = (int)g_hash_table_lookup(table, (gpointer) cpsid);
    int result = 0;

    if (status_check_mask(status, mask)) {
	result = 1;
	status_send(conn, cpsid, status);
	if (steady1_or_next0 == 0) {
	    mask &= ~status_to_mask(status);
	    g_hash_table_insert(table, (gpointer) cpsid, (gpointer) mask);
	}
	if (DGS_DEBUG)
	    fprintf(stderr, "Send status event: %s\n",
		    util_status_enum2str(status));
    }
    return result;
}

private void
status_send(gsdpsx_conn_info * conn, int cpsid, int status)
{
    XEvent event;
    DPSCAPStatusEvent *status_event;
    static Atom statusAtom = 0;
    Display * curr_dpy;

    CONTEXTXID cxid =
	(CONTEXTXID) g_hash_table_lookup(conn->cpsid_to_cxid_table,
					 (void *)cpsid);

    if (!(curr_dpy = DGS_get_current_display()) || !DGS_current_device_is_x11())
	return;

    if (!statusAtom)
	statusAtom = XInternAtom(curr_dpy, DPSCAP_TYPE_PSSTATUS, False);

    event.xclient.type = ClientMessage;
    event.xclient.serial = 0;
    event.xclient.send_event = True;
    event.xclient.message_type = statusAtom;
    event.xclient.format = 8;	/* ??? */
    status_event = (DPSCAPStatusEvent *) event.xclient.data.l;

    status_event->status = status;
    if (util_byteorder_check(conn)) {
	status_event->cxid = htonl(cxid);
	status_event->sequenceNumber = htons(0);
    } else {
	status_event->cxid = cxid;
	status_event->sequenceNumber = 0;
    }
    event.xclient.display = curr_dpy;
    event.xclient.window = conn->clientWindow;
    if (0)
	g_message("[%s]", "status_send");
    XSendEvent(event.xclient.display, event.xclient.window, False, 0, &event);
    XSync(curr_dpy, False);
}

private int
status_check_mask(int status, int mask)
{
    return (status_to_mask(status) & mask);
}
private int
status_to_mask(int status)
{
    return 1 << (status - 1);
}
