/* $Xorg: lbxcmap.c,v 1.4 2001/02/09 02:05:16 xorgcvs Exp $ */

/*
Copyright 1996, 1998  The Open Group

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of The Open Group shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from The Open Group.
*/
/* $XFree86: xc/programs/Xserver/lbx/lbxcmap.c,v 1.9 2001/08/23 14:46:57 alanh Exp $ */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include <sys/types.h>
#define NEED_REPLIES
#define NEED_EVENTS
#include <X11/X.h>
#include <X11/Xproto.h>
#include <X11/Xos.h>
#include "misc.h"
#include "os.h"
#include "dixstruct.h"
#include "resource.h"
#include "scrnintstr.h"
#include "colormapst.h"
#include "propertyst.h"
#define _XLBX_SERVER_
#include <X11/extensions/lbxstr.h>
#include "lbxserve.h"
#include <X11/Xfuncproto.h>
#include <stdio.h>

static int lbxScreenPrivIndex;		/* lbx screen private index */
static int lbxColormapPrivIndex;	/* lbx colormap private index */

typedef struct {			/* lbx screen private */
    CreateColormapProcPtr CreateColormap;
    DestroyColormapProcPtr DestroyColormap;
  CloseScreenProcPtr CloseScreen;
} LbxScreenPriv;

typedef struct _LbxStalled {
    XID id;
    struct _LbxStalled *next;
} LbxStalled;

typedef struct _LbxColormapPriv {	/* lbx colormap private */
    char	grab_status;
    char	smart_grab;
    LbxProxyPtr	grabber;
    int		last_grabber;	/* uid, not pid */
    LbxStalled	*stalled_clients;
    ColormapPtr	next;		/* proxy chain */
} LbxColormapPriv;

#define CMAP_NOT_GRABBED	0
#define CMAP_GRABBED		1
#define CMAP_WAITING_FOR_UNGRAB	2

static int LbxUnstallClient(pointer data, XID id);

static RESTYPE StalledResType;

/*
 * Initialize the fields in the colormap private allocated for LBX.
 */

static LbxColormapPriv *
LbxColormapPrivInit (ColormapPtr pmap)
{
    LbxColormapPriv *cmapPriv;

    cmapPriv = (LbxColormapPriv *) xalloc (sizeof (LbxColormapPriv));
    if (!cmapPriv)
	return cmapPriv;

    pmap->devPrivates[lbxColormapPrivIndex].ptr = (pointer) cmapPriv;

    cmapPriv->grab_status = CMAP_NOT_GRABBED;
    cmapPriv->grabber = NULL;
    cmapPriv->last_grabber = 0;
    cmapPriv->smart_grab = FALSE;
    cmapPriv->stalled_clients = NULL;
    cmapPriv->next = NULL;

    return cmapPriv;
}


static int
LbxDefCmapPrivInit (ColormapPtr pmap, int index)
{
#if 0
    /* BUG: You can't do that. lbxColormapPrivIndex hasn't 
	been initialized yet.  */
    pmap->devPrivates[lbxColormapPrivIndex].ptr = NULL;
#endif
    return 1;
}

static Bool
LbxCreateColormap (ColormapPtr pmap)
{
    ScreenPtr pScreen = pmap->pScreen;
    Bool ret;

    pScreen->CreateColormap = ((LbxScreenPriv *) (pScreen->devPrivates[
	lbxScreenPrivIndex].ptr))->CreateColormap;

    pmap->devPrivates[lbxColormapPrivIndex].ptr = NULL;
    ret = (*pScreen->CreateColormap) (pmap);
 
    pScreen->CreateColormap = LbxCreateColormap;

    return ret;
}

static void
LbxDestroyColormap (ColormapPtr pmap)
{
    ScreenPtr pScreen = pmap->pScreen;

    LbxReleaseCmap(pmap, FALSE);
    pScreen->DestroyColormap = ((LbxScreenPriv *) (pScreen->devPrivates[
	lbxScreenPrivIndex].ptr))->DestroyColormap;
    (*pScreen->DestroyColormap) (pmap);
    if (pmap->devPrivates && pmap->devPrivates[lbxColormapPrivIndex].ptr)
	xfree(pmap->devPrivates[lbxColormapPrivIndex].ptr);
    pScreen->DestroyColormap = LbxDestroyColormap;
}

static Bool
LbxCloseScreen(int i, ScreenPtr pScreen)
{
    LbxScreenPriv* pLbxScrPriv = ((LbxScreenPriv *) 
			     (pScreen->devPrivates[lbxScreenPrivIndex].ptr));
    
    pScreen->CloseScreen = pLbxScrPriv->CloseScreen;

    xfree(pScreen->devPrivates[lbxScreenPrivIndex].ptr);
    pScreen->devPrivates[lbxScreenPrivIndex].ptr = NULL;

    return pScreen->CloseScreen(i, pScreen);
}

/*
 * Initialize LBX colormap private.
 */

int
LbxCmapInit (void)

{
    LbxScreenPriv *pScreenPriv;
    ColormapPtr defMap;
    ScreenPtr pScreen;
    int i;

    StalledResType = CreateNewResourceType(LbxUnstallClient);

    lbxScreenPrivIndex = AllocateScreenPrivateIndex ();
    if (lbxScreenPrivIndex < 0)
	return 0;

    lbxColormapPrivIndex = AllocateColormapPrivateIndex (LbxDefCmapPrivInit);
    if (lbxColormapPrivIndex < 0)
	return 0;

    for (i = 0; i < screenInfo.numScreens; i++)
    {
	pScreen = screenInfo.screens[i];

        defMap = (ColormapPtr) LookupIDByType(
			pScreen->defColormap, RT_COLORMAP);

	/* now lbxColormapPrivIndex exists */
        defMap->devPrivates[lbxColormapPrivIndex].ptr = NULL;

	pScreenPriv = (LbxScreenPriv *) xalloc (sizeof (LbxScreenPriv));
	if (!pScreenPriv)
	    return 0;

	pScreenPriv->CreateColormap = pScreen->CreateColormap;
	pScreen->CreateColormap = LbxCreateColormap;
	pScreenPriv->DestroyColormap = pScreen->DestroyColormap;
	pScreen->DestroyColormap = LbxDestroyColormap;
	pScreenPriv->CloseScreen = pScreen->CloseScreen;
	pScreen->CloseScreen = LbxCloseScreen;
	pScreen->devPrivates[lbxScreenPrivIndex].ptr = (pointer) pScreenPriv;
    }

    return 1;
}

/*
 * Return the number of allocated cells in the PSEUDO colormap.
 */

static int
NumAllocatedCells (EntryPtr pent,
		   int size)
{
    Pixel pixel;
    int count = 0;

    for (pixel = 0; pixel < size; pixel++)
    {
	if (pent[pixel].refcnt != 0 && pent[pixel].refcnt != AllocTemporary)
	    count++;
    }

    return count;
}

#define NUMRED(vis) ((vis->redMask >> vis->offsetRed) + 1)
#define NUMGREEN(vis) ((vis->greenMask >> vis->offsetGreen) + 1)
#define NUMBLUE(vis) ((vis->blueMask >> vis->offsetBlue) + 1)

#define PIX_OUT(ptr,is2,val) \
    if (is2) *ptr++ = (val) >> 8; \
    *ptr++ = (val)
#define RGB_OUT(ptr,is2,shift,val) \
    *ptr++ = (val) >> shift; \
    if (is2) *ptr++ = (val)

/*
 * Return the list of allocated cells in the channel in
 * the format required by LbxGrabCmapReply.
 */

static CARD8 *
OutputChannel(ColormapPtr pmap,
	      EntryPtr chan,
	      int size,
	      CARD8 *ptr,
	      CARD8 flags,
	      CARD8 channels)
{
    Bool	px2;
    Bool	rgb2;
    int		shift;
    int		rgb_sz;
    Pixel	pixel;
    EntryPtr	pent;
    CARD8 *pixel_private_range_ptr = NULL;
    CARD8 *pixel_shared_range_ptr = NULL;
    int		allocpriv;

    px2 = (flags & LBX_2BYTE_PIXELS) != 0;
    rgb2 = (flags & LBX_RGB_BITS_MASK) > 7;
    if (rgb2)
	shift = 8;
    else
	shift = 15 - (flags & LBX_RGB_BITS_MASK);
    rgb_sz = rgb2 + 1;
    if (channels == (DoRed|DoGreen|DoBlue))
	rgb_sz *= 3;
    /* kinda gross, but ddxen use AllocAll on static maps */
    allocpriv = (pmap->pVisual->class & DynamicClass) ? AllocPrivate : 0;
    for (pixel = 0; pixel < size; pixel++)
    {
	pent = (EntryPtr) &chan[pixel];

	if (pent->refcnt == 0 || pent->refcnt == AllocTemporary)
	{
	    /*
	     * A free pixel.  This disrupts all ranges.
	     */

	    pixel_private_range_ptr = pixel_shared_range_ptr = NULL;

	    continue;
	}
	else if (pent->refcnt == allocpriv)
	{
	    /*
	     * A private pixel.  This disrupts any PIXEL_SHARED_RANGE.
	     */

	    pixel_shared_range_ptr = NULL;

	    if (!pixel_private_range_ptr)
	    {
		pixel_private_range_ptr = ptr;

		*ptr++ = LBX_PIXEL_PRIVATE;
		PIX_OUT(ptr, px2, pixel);
	    }
	    else
	    {
		CARD8 *pos = pixel_private_range_ptr + 2 + px2;
		if (*pixel_private_range_ptr == LBX_PIXEL_PRIVATE) {
		    *pixel_private_range_ptr = LBX_PIXEL_RANGE_PRIVATE;
		    ptr += 1 + px2;
		}
		PIX_OUT(pos, px2, pixel);
	    }
	}
	else
	{
	    /*
	     * A shared pixel.  This disrupts any PIXEL_PRIVATE_RANGE.
	     */

	    pixel_private_range_ptr = NULL;

	    if (!pixel_shared_range_ptr)
	    {
		pixel_shared_range_ptr = ptr;

		*ptr++ = LBX_PIXEL_SHARED;
		PIX_OUT(ptr, px2, pixel);
	    }
	    else
	    {
		CARD8 *pos = pixel_shared_range_ptr + 2 + px2;
		if (*pixel_shared_range_ptr == LBX_PIXEL_SHARED)
		{
		    *pixel_shared_range_ptr = LBX_PIXEL_RANGE_SHARED;
		    memmove (pos + 1 + px2, pos, rgb_sz);
		    ptr += 1 + px2;
		}
		PIX_OUT(pos, px2, pixel);
	    }

	    if (channels & DoRed) {
		RGB_OUT(ptr, rgb2, shift, pent->co.local.red);
	    }
	    if (channels & DoGreen) {
		RGB_OUT(ptr, rgb2, shift, pent->co.local.green);
	    }
	    if (channels & DoBlue) {
		RGB_OUT(ptr, rgb2, shift, pent->co.local.blue);
	    }
	}
    }
    return ptr;
}

static void
GetAllocatedCells (ColormapPtr pmap,
		   CARD8 *flags,
		   CARD8 *buf,
		   int *bytes)
{
    CARD8	*ptr;

    *flags = pmap->pVisual->bitsPerRGBValue - 1;
    if (pmap->pVisual->ColormapEntries > 256)
	*flags |= LBX_2BYTE_PIXELS;
    if (!(pmap->pVisual->class & DynamicClass))
	*flags |= LBX_AUTO_RELEASE;
    if ((pmap->pVisual->class | DynamicClass) == DirectColor) {
	*flags |= LBX_3CHANNELS;
	ptr = OutputChannel(pmap, pmap->red, NUMRED(pmap->pVisual),
			    buf, *flags, DoRed);
	*ptr++ = LBX_NEXT_CHANNEL;
	ptr = OutputChannel(pmap, pmap->green, NUMGREEN(pmap->pVisual),
			    ptr, *flags, DoGreen);
	*ptr++ = LBX_NEXT_CHANNEL;
	ptr = OutputChannel(pmap, pmap->blue, NUMBLUE(pmap->pVisual),
			    ptr, *flags, DoBlue);
    } else
	ptr = OutputChannel(pmap, pmap->red, pmap->pVisual->ColormapEntries,
			    buf, *flags, DoRed|DoGreen|DoBlue);
    *ptr++ = LBX_LIST_END;
    *bytes = ptr - buf;
}


/*
 * Send an LbxReleaseCmapEvent to a proxy.
 */

static void
SendReleaseCmapEvent (LbxProxyPtr proxy,
		      Colormap cmap)
{
    xLbxReleaseCmapEvent ev;
    ClientPtr client;
    LbxClientPtr lbxcp;
    int n;

    lbxcp = proxy->lbxClients[0];

    if (lbxcp && (client = lbxcp->client))
    {
	ev.type = LbxEventCode;
	ev.lbxType = LbxReleaseCmapEvent;
	ev.sequenceNumber = client->sequence;
	ev.colormap = cmap;
	ev.pad1 = ev.pad2 = ev.pad3 = ev.pad4 = ev.pad5 = ev.pad6 = 0;

	if (client->swapped)
	{
	    swaps(&ev.sequenceNumber, n);
	    swapl(&ev.colormap, n);
	}

	WriteToClient(client, sz_xLbxReleaseCmapEvent, (char *) &ev);
	LbxForceOutput(proxy);

#ifdef COLOR_DEBUG
	fprintf (stderr,
	    "Sent LbxReleaseCmapEvent to proxy %d, seq = 0x%x, cmap = 0x%x\n",
	    proxy->pid, client->sequence, cmap);
#endif
    }
}


/*
 * WaitForServerCmapControl checks if the colormap is grabbed by a proxy,
 * and if so, sends an LbxReleaseCmapEvent to the proxy.  It then suspends
 * the current request until the server gets the ReleaseCmap message from
 * the proxy.
 */

static Bool
WaitForServerCmapControl (ClientPtr client,
			  ColormapPtr pmap)
{
    LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);
    LbxStalled *stalled;

    if (cmapPriv->grab_status == CMAP_GRABBED)
    {
	/*
	 * Send an LbxReleaseCmapEvent to the proxy that has the grab.
	 */

	SendReleaseCmapEvent (cmapPriv->grabber, pmap->mid);
	cmapPriv->grab_status = CMAP_WAITING_FOR_UNGRAB;
    }

    stalled = (LbxStalled *)xalloc(sizeof(LbxStalled));
    if (!stalled)
	return FALSE;
    stalled->id = FakeClientID(client->index);
    stalled->next = cmapPriv->stalled_clients;
    cmapPriv->stalled_clients = stalled;
    return AddResource(stalled->id, StalledResType, (pointer)cmapPriv);
}


/*
 * When the X server gets any of the requests that allocate color cells,
 * it calls LbxCheckColorRequest on the request.  This function will check
 * if the colormap is grabbed by a proxy, and if so, will suspend the
 * current request and wait for the proxy to release the colormap.
 */

Bool
LbxCheckColorRequest (ClientPtr client,
		      ColormapPtr pmap,
		      xReq *req)
{
    LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);

    if (!cmapPriv)
	return FALSE;

    if (cmapPriv->grab_status != CMAP_NOT_GRABBED)
    {
	/*
	 * The colormap is grabbed by a proxy.  Reset this request, and
	 * process it after the server gets back control of the colormap.
	 * Before we reset the request, we must put it back in the
	 * client's byte order.
	 */

	if (!WaitForServerCmapControl (client, pmap))
	    return FALSE;

	if (client->swapped)
	{
	    register int n;

	    switch (req->reqType)
	    {
	    case X_AllocColor:
	    {
		xAllocColorReq *stuff = (xAllocColorReq *) req;
		swaps(&stuff->length, n);
		swapl(&stuff->cmap, n);
		swaps(&stuff->red, n);
		swaps(&stuff->green, n);
		swaps(&stuff->blue, n);
		break;
	    }
	    case X_AllocNamedColor:
	    {
		xAllocNamedColorReq *stuff = (xAllocNamedColorReq *) req;
		swaps(&stuff->length, n);
		swapl(&stuff->cmap, n);
		swaps(&stuff->nbytes, n);
		break;
	    }
	    case X_AllocColorCells:
	    {
		xAllocColorCellsReq *stuff = (xAllocColorCellsReq *) req;
		swaps(&stuff->length, n);
		swapl(&stuff->cmap, n);
		swaps(&stuff->colors, n);
		swaps(&stuff->planes, n);
		break;
	    }
	    case X_AllocColorPlanes:
	    {
		xAllocColorPlanesReq *stuff = (xAllocColorPlanesReq *) req;
		swaps(&stuff->length, n);
		swapl(&stuff->cmap, n);
		swaps(&stuff->colors, n);
		swaps(&stuff->red, n);
		swaps(&stuff->green, n);
		swaps(&stuff->blue, n);
		break;
	    }
	    default:
		break;
	    }
	}

	ResetCurrentRequest(client);
	client->sequence--;
	IgnoreClient(client);

	return TRUE;
    }

    if (!LbxClient(client) ||
	LbxProxy(client)->uid != cmapPriv->last_grabber)
    {
	/*
	 * Next time the proxy for this client does a colormap grab, it
	 * will have to get the colormap state (a non-smart grab).
	 */

	cmapPriv->smart_grab = FALSE;
    }

    return FALSE;
}

static Bool
LbxGrabbedByClient (ClientPtr client,
		    ColormapPtr pmap)
{
    LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);
    return (cmapPriv &&
	    (cmapPriv->grab_status != CMAP_NOT_GRABBED) &&
	    (cmapPriv->grabber == LbxProxy(client)));
}

/*
 * Check if a colormap is grabbed by a proxy.
 */

int
LbxCheckCmapGrabbed (ColormapPtr pmap)
{
    LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);

    return (cmapPriv && (cmapPriv->grab_status == CMAP_GRABBED));
}


/*
 * Disable a smart grab on the specified colormap.
 */

void
LbxDisableSmartGrab (ColormapPtr pmap)
{
    LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);

    if (cmapPriv)
	cmapPriv->smart_grab = FALSE;
}


/*
 * Send an LbxFreeCellsEvent to the specified proxy.
 */

static void
SendFreeCellsEvent (LbxProxyPtr proxy,
		    Colormap cmap,
		    Pixel pixel_start,
		    Pixel pixel_end)
{
    xLbxFreeCellsEvent ev;
    ClientPtr client;
    LbxClientPtr lbxcp;
    int n;

    lbxcp = proxy->lbxClients[0];

    if (lbxcp && (client = lbxcp->client))
    {
	ev.type = LbxEventCode;
	ev.lbxType =  LbxFreeCellsEvent;
	ev.sequenceNumber = client->sequence;
	ev.colormap = cmap;
	ev.pixelStart = pixel_start;
	ev.pixelEnd = pixel_end;
	ev.pad1 = ev.pad2 = ev.pad3 = ev.pad4 = 0;

	if (client->swapped)
	{
	    swaps(&ev.sequenceNumber, n);
	    swapl(&ev.colormap, n);
	    swapl(&ev.pixelStart, n);
	    swapl(&ev.pixelEnd, n);
	}

	WriteToClient(client, sz_xLbxFreeCellsEvent, (char *) &ev);
	LbxForceOutput(proxy);
#ifdef COLOR_DEBUG
	fprintf (stderr, "Sent LbxFreeCellsEvent to proxy %d, seq = 0x%x\n",
	    proxy->pid, client->sequence);
	fprintf (stderr, "   cmap = 0x%x, pixelStart = %d, pixelEnd = %d\n",
	    cmap, pixel_start, pixel_end);
#endif
    }
}

/* XXX use of globals like this is gross */
static long pixel_start;
static long pixel_end;


/*
 * LbxFreeCellsEvent generation functions.
 */

/*ARGSUSED*/
void
LbxBeginFreeCellsEvent (ColormapPtr pmap)
{
    pixel_start = -1;
    pixel_end = -1;
}


void
LbxAddFreeCellToEvent (ColormapPtr pmap,
		       Pixel pixel)
{
    /*
     * We must notify the proxy that has this colormap
     * grabbed which cells are being freed (their refcount
     * has reached zero).
     */

    if (pixel_start == -1)
	pixel_start = pixel;
    else
    {
	if (pixel_end == -1)
	    pixel_end = pixel;
	else
	{
	    if (pixel_end + 1 == pixel)
		pixel_end = pixel;
	    else if (pixel > pixel_end + 1)
	    {
		LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
		    (pmap->devPrivates[lbxColormapPrivIndex].ptr);

		SendFreeCellsEvent (cmapPriv->grabber,
				    pmap->mid, pixel_start, pixel_end);

		pixel_start = pixel;
		pixel_end = -1;
	    }
	}
    }
}

void
LbxEndFreeCellsEvent (ColormapPtr pmap)
{
    /*
     * Check if there is an LbxFreeCellEvent we need to write.
     */

    if (pixel_start != -1)
    {
	LbxColormapPriv *cmapPriv = (LbxColormapPriv *)
	    (pmap->devPrivates[lbxColormapPrivIndex].ptr);

	SendFreeCellsEvent (cmapPriv->grabber,
			    pmap->mid, pixel_start,
			    pixel_end == -1 ? pixel_start : pixel_end);
    }
}


/*
 * Sort the specified pixel list.  This optimizes generation
 * of LbxFreeCellsEvent.
 */

void
LbxSortPixelList (Pixel *pixels,
		  int count)
{
     int i, j;

     for (i = 0; i <= count - 2; i++)
	 for (j = count - 1; j > i; j--)
	    if (pixels[j - 1] > pixels[j])
	    {
		Pixel temp = pixels[j - 1];
		pixels[j - 1] = pixels[j];
		pixels[j] = temp;
	    }
}


/*
 * Handle a colormap grab request from a proxy.
 */

int
ProcLbxGrabCmap(ClientPtr client)
{
    REQUEST(xLbxGrabCmapReq);
    xLbxGrabCmapReply *reply;
    Bool smartGrab;
    LbxColormapPriv *cmapPriv;
    ColormapPtr pmap;
    int bytes, n;
    LbxProxyPtr proxy = LbxProxy(client);

    client->sequence--;		/* not a counted request */

    pmap = (ColormapPtr) SecurityLookupIDByType (client, stuff->cmap,
						 RT_COLORMAP,
						 SecurityWriteAccess);

    if (!pmap)
    {
        client->errorValue = stuff->cmap;
	return BadColor;
    }

    cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);
    if (!cmapPriv)
    {
	cmapPriv = LbxColormapPrivInit (pmap);
	if (!cmapPriv)
	    return BadAlloc;
    }


    /*
     * We have a SMART GRAB if since this proxy last ungrabbed the
     * colormap, no color cell was alloc'd by an entity other than
     * this proxy (this includes other proxies as well as clients
     * directly connected to the X server without a proxy).
     *
     * We want to optimize this special case because a proxy may give
     * up a grab because it got a request that it could not handle
     * (e.g. AllocNamedColor or LookupColor).  When it asks back for
     * the grab, there is no need for the server to send the colormap
     * state, because the proxy is already up to date on the state of
     * the colormap.
     *
     * In order for this to work, the following assumptions are made
     * about the proxy:
     *
     * - the proxy is kept up to date on all cell allocations made on its
     *   behalf resulting from the following requests: AllocNamedColor, 
     *   AllocColorCells, AllocColorPlanes
     * - the proxy is kept up to date on all cells freed by any client
     *   via the LbxFreeCell event.
     */

    /* if proxy is this confused, give it full info */
    if (cmapPriv->grab_status == CMAP_GRABBED && cmapPriv->grabber == proxy)
	LbxReleaseCmap(pmap, FALSE);

    if (proxy->uid != cmapPriv->last_grabber)
	cmapPriv->smart_grab = FALSE;
    smartGrab = cmapPriv->smart_grab;

#ifdef COLOR_DEBUG
    fprintf (stderr, "\nGot colormap grab request, ");
    fprintf (stderr, "seq = 0x%x, proxy = %d, client = %d, cmap = 0x%x\n",
	client->sequence, proxy->pid, client->index, stuff->cmap);

    if (cmapPriv->grab_status == CMAP_NOT_GRABBED)
    {
	fprintf (stderr, "cmap 0x%x is not grabbed by any proxy\n",
	    stuff->cmap);
	if (smartGrab)
	    fprintf (stderr, "This is a smart grab\n");
    }
    else if (cmapPriv->grab_status == CMAP_GRABBED)
    {
	if (cmapPriv->grabber == proxy)
	{
	    fprintf (stderr, "cmap 0x%x is already grabbed by proxy %d\n",
	        stuff->cmap, proxy->pid);
	}
	else
	{
	    fprintf (stderr, "cmap 0x%x is currently grabbed by proxy %d\n",
	        stuff->cmap, cmapPriv->grabber->pid);
	}
    }
    else if (cmapPriv->grab_status == CMAP_WAITING_FOR_UNGRAB)
    {
	fprintf (stderr,
	    "Already waiting for cmap 0x%x to be ungrabbed by proxy %d\n",
	    stuff->cmap, cmapPriv->grabber->pid);
    }
#endif

    if (cmapPriv->grab_status != CMAP_NOT_GRABBED &&
	cmapPriv->grabber != proxy)
    {
	/*
	 * The colormap is grabbed by a proxy other than the one that
	 * is requesting this grab.  Reset this grab request, and process
         * it after the server gets back control of the colormap.  Before
	 * we reset the request, we must put it back in the client's byte
	 * order.
	 */

	if (!WaitForServerCmapControl (client, pmap))
	    return BadAlloc;

	if (client->swapped)
	{
	    swaps(&stuff->length, n);
	    swapl(&stuff->cmap, n);
	}

	ResetCurrentRequest(client);
	IgnoreClient(client);

	return Success;
    }

    if (pmap->pVisual->class & DynamicClass) {
	cmapPriv->grabber = proxy;
	cmapPriv->grab_status = CMAP_GRABBED;
	cmapPriv->next = proxy->grabbedCmaps;
	proxy->grabbedCmaps = pmap;
    } else
	smartGrab = FALSE;

    /*
     * For an smart grab (see comments above), there is no information
     * sent about the colormap cells because the proxy is all up to date.
     * Otherwise, the server sends the proxy the state of all allocated
     * cells in the colormap.  All cells not specified are assumed free.
     */

    bytes = 0;
    if (!smartGrab) {
	if ((pmap->pVisual->class | DynamicClass) == DirectColor) {
	    bytes = NumAllocatedCells(pmap->red,
				      NUMRED(pmap->pVisual)) * 9;
	    bytes += NumAllocatedCells(pmap->green,
				       NUMGREEN(pmap->pVisual)) * 9;
	    bytes += NumAllocatedCells(pmap->blue,
				       NUMBLUE(pmap->pVisual)) * 9;
	    bytes += 2;
	} else
	    bytes = NumAllocatedCells(pmap->red,
				      pmap->pVisual->ColormapEntries) * 9;
    }
    bytes += sz_xLbxGrabCmapReply + 1;
    reply = (xLbxGrabCmapReply *) xalloc (bytes);
    bzero (reply, sz_xLbxGrabCmapReply);

    if (smartGrab)
    {
	reply->flags = LBX_SMART_GRAB;
	reply->length = 0;
    }
    else
    {
	GetAllocatedCells (pmap, &reply->flags,
			   (CARD8 *) reply + sz_xLbxGrabCmapReplyHdr, &bytes);
	if (bytes <= (sz_xLbxGrabCmapReply - sz_xLbxGrabCmapReplyHdr))
	    reply->length = 0;
	else
	    reply->length = (sz_xLbxGrabCmapReplyHdr +
		bytes - sz_xLbxGrabCmapReply + 3) >> 2;
    }

    reply->type = X_Reply;
    reply->sequenceNumber = client->sequence;

    bytes = sz_xLbxGrabCmapReply + (reply->length << 2);

    if (client->swapped)
    {
	register char n;

	swaps (&reply->sequenceNumber, n);
	swapl (&reply->length, n);
    }

    WriteToClient (client, bytes, (char *)reply);
    
    xfree (reply);

    return (client->noClientException);
}

static int
LbxUnstallClient(pointer     data,
		 XID         id)
{
    LbxColormapPriv *cmapPriv = (LbxColormapPriv *)data;
    LbxStalled **prev;
    ClientPtr client;

    for (prev = &cmapPriv->stalled_clients; *prev && (*prev)->id != id; )
	prev = &(*prev)->next;
    *prev = (*prev)->next;
    client = clients[CLIENT_ID(id)];
    if (!client->clientGone)
	AttendClient(client);
    return 0;
}

void
LbxReleaseCmap(ColormapPtr pmap,
	       Bool smart)
{
    LbxColormapPriv *cmapPriv;
    ColormapPtr *prev;

    if (!pmap->devPrivates)
	return;
    cmapPriv = (LbxColormapPriv *)
	(pmap->devPrivates[lbxColormapPrivIndex].ptr);
    if (!cmapPriv || (cmapPriv->grab_status == CMAP_NOT_GRABBED))
	return;

    for (prev = &cmapPriv->grabber->grabbedCmaps; *prev && *prev != pmap; )
	prev = &((LbxColormapPriv *)
		 (*prev)->devPrivates[lbxColormapPrivIndex].ptr)->next;
    if (*prev == pmap)
	*prev = cmapPriv->next;

    while (cmapPriv->stalled_clients)
	FreeResource(cmapPriv->stalled_clients->id, 0);

    cmapPriv->grab_status = CMAP_NOT_GRABBED;
    cmapPriv->last_grabber = smart ? cmapPriv->grabber->uid : 0;
    cmapPriv->grabber = NULL;
    cmapPriv->smart_grab = smart;
}

/*
 * Handle a colormap release request from a proxy.
 */

int
ProcLbxReleaseCmap(ClientPtr	client)
{
    REQUEST(xLbxReleaseCmapReq);
    ColormapPtr pmap;

#ifdef COLOR_DEBUG
    fprintf (stderr, "Got colormap release request, ");
    fprintf (stderr, "seq = 0x%x, proxy = %d, client = %d, cmap = 0x%x\n",
	client->sequence, LbxProxyID(client), client->index, stuff->cmap);
#endif

    pmap = (ColormapPtr) SecurityLookupIDByType (client, stuff->cmap,
						 RT_COLORMAP,
						 SecurityWriteAccess);

    if (!pmap)
    {
        client->errorValue = stuff->cmap;
	return BadColor;
    }

    if (LbxGrabbedByClient(client, pmap))
	LbxReleaseCmap(pmap, TRUE);
    return Success;
}


/*
 * Handle an LbxAllocColor request.  The proxy did the alloc and
 * is telling the server what rgb and pixel value to use.
 */

int
ProcLbxAllocColor(ClientPtr	client)
{
    REQUEST(xLbxAllocColorReq);
    ColormapPtr pmap;
    CARD32 pixel = stuff->pixel;

    REQUEST_SIZE_MATCH (xLbxAllocColorReq);
    pmap = (ColormapPtr) SecurityLookupIDByType (client, stuff->cmap,
						 RT_COLORMAP,
						 SecurityWriteAccess);

#ifdef COLOR_DEBUG
    fprintf (stderr,
	"Got LBX alloc color: seq = 0x%x, proxy = %d, client = %d\n",
	client->sequence, LbxProxyID(client), client->index);
    fprintf (stderr, "  cmap = 0x%x, pixel = %d, rgb = (%d,%d,%d)\n",
        stuff->cmap, stuff->pixel, stuff->red, stuff->green, stuff->blue);
#endif

    if (pmap)
    {
	int status;
	if (!LbxGrabbedByClient(client, pmap))
	    return BadAccess;

	status = AllocColor (pmap,
	    &stuff->red, &stuff->green, &stuff->blue,
	    &pixel, client->index);

	if (status == Success && pixel != stuff->pixel)
	{
	    /*
	     * Internal error - Proxy allocated different pixel from server
	     */
#ifdef COLOR_DEBUG
	    fprintf(stderr, "got pixel %d (%d, %d, %d), expected %d\n",
		    pixel, stuff->red, stuff->green, stuff->blue, stuff->pixel);
#endif
	    FreeColors (pmap, client->index, 1, &pixel, 0L);
	    return BadAlloc;
	}

	return status;
    }
    else
    {
        client->errorValue = stuff->cmap;
        return (BadColor);
    }
}


/*
 * The proxy sends an LbxIncrementPixel request when it short circuits
 * an AllocColor.  The server must bump up the reference count the
 * specified amount.
 */

int
ProcLbxIncrementPixel(ClientPtr	client)
{
    REQUEST(xLbxIncrementPixelReq);
    ColormapPtr pmap;
    EntryPtr    pent;
    Pixel	pixel;
    unsigned	short red, green, blue;
    VisualPtr	pVisual;
    int		status;

#ifdef COLOR_DEBUG
    fprintf (stderr,
	"Got LBX increment pixel: seq = 0x%x, proxy = %d, client = %d\n",
	client->sequence, LbxProxyID(client), client->index);
    fprintf (stderr, "  cmap = 0x%x, pixel = %d\n",
        stuff->cmap, stuff->pixel);
#endif

    /*
     * Looks up the color associated with the pixel, and then call
     * AllocColor() - a bit round-about, but it should work.
     */

    pmap = (ColormapPtr) SecurityLookupIDByType(client, stuff->cmap,
						RT_COLORMAP,
						SecurityWriteAccess);
    if (!pmap) {
        client->errorValue = stuff->cmap;
	return BadColor;
    }

    pixel = stuff->pixel;

    switch (pmap->class) {
    case StaticColor:
    case StaticGray:
	red = pmap->red[pixel].co.local.red;
	green = pmap->red[pixel].co.local.green;
	blue = pmap->red[pixel].co.local.blue;
	break;
    case GrayScale:
    case PseudoColor:
	pent = pmap->red + pixel;
	red = pent->co.local.red;
	green = pent->co.local.green;
	blue = pent->co.local.blue;
	break;
    default:
	pVisual = pmap->pVisual;
	red = pmap->red[(pixel & pVisual->redMask) >> pVisual->offsetRed].co.local.red;
	green = pmap->green[(pixel & pVisual->greenMask) >> pVisual->offsetGreen].co.local.green;
	blue = pmap->blue[(pixel & pVisual->blueMask) >> pVisual->offsetBlue].co.local.blue;
	break;
    }

    status = AllocColor(pmap, &red, &green, &blue, &pixel, client->index);

    if (status == Success && pixel != stuff->pixel)
    {
	/*
	 * Internal error - Proxy allocated different pixel from server
	 */
#ifdef COLOR_DEBUG
	fprintf(stderr, "got pixel %d, expected %d\n", pixel, stuff->pixel);
#endif
	FreeColors (pmap, client->index, 1, &pixel, 0L);
	return BadAlloc;
    }

    return status;
}
